Skip to main content

Getting the SDK

Now that we have a program that we can run, it's time to integrate the game's API.

Auto Traffic Control uses the gRPC framework to provide a well-defined and easy-to-use API to players. One feature of gRPC is that API clients can be auto-generated from the schema definitions of the API. These clients hide the internal details of gRPC, and give you a high-level interface for interacting with the game.

We publish these auto-generated client libraries for the most popular languages, and call them our software development kits (SDKs).

tip

The API documentation provides an overview of the different data types, services, and endpoints of the API.

Adding the npm package

Auto Traffic Control's SDK for the Node ecosystem is called available as a npm package. Click the badge below to open the package on npmjs.com:

npm package

As you can see on npmjs.com, you can add the package as a dependency to your project with the following command:

npm install auto-traffic-control

Initializing the client

The game's API is divided into different services, which have their own area of responsibility. For a quick proof-of-concept, we're going to call the AtcService to query the version of the game.

Open src/main.ts, and remove the following line from the main function:

console.log("Hello, World!");

Let's start by initializing the AtcService. The SDK contains the AtcServiceClient, which can be used to connect to the service. Pass in the address of the game server, and call the helper method getCredentials in the SDK to authenticate with the server.

function main() {
const atcService = new AtcServiceClient("localhost:4747", getCredentials());
}

Depending on your editor or IDE, you will get a TS2304 error now, because the names AtcServiceClient and getCredentials cannot be found. We have to import them from the SDK first. Add the following at the top of src/main.ts:

import { getCredentials, AtcServiceClient } from "auto-traffic-control";

If you run the program with npm start now, nothing will happen. You initialized the client, but it only connects to the server when we call one of its APIs. Let's do that next.

note

Authentication is a core functionality in gRPC, and the client stubs require credentials. You don't need to worry about this, just remember to get the credentials from the SDK and pass them as the second argument when initializing a client.

Getting the version

If you check the documentation of the AtcService, you will see that it only has a single API endpoint: getVersion. You can actually inspect the generated code inside the SDK to see the full method definition:

public getVersion(
request: GetVersionRequest,
callback: (error: grpc.ServiceError | null, response: GetVersionResponse) => void
): grpc.ClientUnaryCall;

As you can see, this method takes a GetVersionRequest as its first parameter. The second parameter is a callback function that is passed either a ServiceError or a GetVersionResponse.

Let's set up the API call with an empty callback method:

function main() {
const atcService = new AtcServiceClient("localhost:4747", getCredentials());

atcService.getVersion(
new GetVersionRequest(),
(err: ServiceError | null, response: GetVersionResponse) => {}
);
}

If your editor or IDE is not already telling you, don't forget to add ServiceError and GetVersionResponse to the imports again.

Handling errors

The callback function is passed a ServiceError when the API request failed. For example, when the game is not running and the program cannot connect to the game server. If that happens, we want to print the error and abort the program. Luckily, we can simply throw an exception to achieve this:

(err: ServiceError | null, response: GetVersionResponse) => {
if (err != null) {
throw err;
}
};

If you run the program with npm start now (without having started the game), you will get the following error:

Error: 14 UNAVAILABLE: No connection established

Throwing an exception is not a pretty way to handle the issue, but it's good enough for now.

Unpacking the response

If the API request succeeds, the callback gets passed a GetVersionResponse. If you check the documentation, you will see that the response has a single field called version. The field has the type Version, which has its own entry in the documentation and looks like this:

message Version {
uint64 major = 1;
uint64 minor = 2;
uint64 patch = 3;
string pre = 4;
}

A slightly annoying but sensible decision in gRPC is that fields can be uninitialized, in which case they are set to a default value. For the version field, this means that it can be undefined. So let's start by getting the version from the GetVersionResponse, and then check if it's set:

const version = response.getVersion();

if (version != null) {
// TODO
} else {
throw new Error("Requesting the version returned an empty response.");
}

I decided to simply throw an exception again, since the program cannot really do anything interesting if the version is empty.

info

The game always sets every field in a response and an uninitialized value is considered a bug.

We can now access the fields of the version and merge them into a single string that we'll print:

if (version != null) {
let versionString = [
version.getMajor(),
version.getMinor(),
version.getPatch(),
].join(".");

if (version.getPre() !== "") {
versionString = versionString.concat(version.getPre());
}

console.log(`Auto Traffic Control is running version '${versionString}'`);
} else {
throw new Error("Requesting the version returned an empty response.");
}

Calling the API

Let's put everything together and make the actual API call. If you followed along, your src/main.ts file should now look something like this:

import {
getCredentials,
AtcServiceClient,
GetVersionRequest,
GetVersionResponse,
ServiceError,
} from "auto-traffic-control";

function main() {
const atcService = new AtcServiceClient("localhost:4747", getCredentials());

atcService.getVersion(
new GetVersionRequest(),
(err: ServiceError | null, response: GetVersionResponse) => {
if (err != null) {
throw err;
}

const version = response.getVersion();

if (version != null) {
let versionString = [
version.getMajor(),
version.getMinor(),
version.getPatch(),
].join(".");

if (version.getPre() !== "") {
versionString = versionString.concat(version.getPre());
}

console.log(
`Auto Traffic Control is running version '${versionString}'`
);
} else {
throw new Error("Requesting the version returned an empty response.");
}
}
);
}

main();

You can run the file with npm start, but you will still get a connection error because we haven't started the game.

Open the itch app, go to your library, and open Auto Traffic Control. Hit the Launch button in the bottom right, and wait for the game to start.

screenshot of the itch page for Auto Traffic Control

With the game open, run npm start. The program will connect to the game server, request the game's version, and the print it to the terminal.

screenshot of a successful API request

info

If you get a connection error while the game is running, try disabling the sandbox in itch's preferences. It might be blocking the incoming connections. Or download the game from itch.io and run it as a standalone binary.

Congrats! 🥳 You sent your first API request to the game.

Now that you know how to use the SDK and its documentation, you are all set to start working on an actual bot. Check out the GameService and try to start the game. Hint: The GameService has a startGame method. 😉

Don't worry if you get stuck. We'll go over it in the next chapter together.