Getting Started - bSkwared/swgoh-comlink GitHub Wiki

Table of Contents

  1. Setup
  2. HMAC Signing
  3. Documentation
  4. Endpoints
  5. Testing

Setup

SWGoH Comlink is provided in several ways:

  • Docker Image: An executable docker image can be obtained via the github container registry at ghcr.io/swgoh-utils/swgoh-comlink:latest. This image can be imported into Docker↗ or any other container runtime environment that accepts Docker Images.
  • Binary executable: A binary can be downloaded from github↗. Binaries are provided on a best effort basis with each release.

It is recommended to run Comlink as a container.

Environment Variables

  • APP_NAME | Required
    The name of your tool/application that will be used to identify your tool in the requests sent to the games APIs. Can also be specified using the --name or -n flag at the command line, but the environment variable method is recommended.
  • PORT | Optional
    Specifies which port to expose. Default is 3000. Can also be specified using the --port or -p flag at the command line. The chosen number is more important to set when not using Docker. When running using docker it doesn't matter as much, you just need to set the external port.
  • ACCESS_KEY - used as the "public" key included in requests from users as part of the HMAC signing process
  • SECRET_KEY - used as the "private" key in the HMAC signing process
  • HMAC_MAX_DRIFT - The amount of time you would like a request to be valid for, in seconds (in the past). Helps prevent replay attacks. Defaults to 30 seconds.
  • HMAC_MIN_DRIFT - The amount of time you would like a request to be valid for, in seconds (in the future). Helps prevent replay attacks. Defaults to 30 seconds.

Deploying with Docker

The ci/cd process automatically publishes images to the github container registry.

Updated images can be pulled and deployed directly from the registry. Existing containers should be stopped and removed using a process similar to the below.

docker pull ghcr.io/swgoh-utils/swgoh-comlink:latest
docker stop swgoh-comlink
docker rm swgoh-comlink
docker run --name swgoh-comlink \
  -d \
  --restart always \
  --network swgoh-comlink \
  --env APP_NAME=your-app-name-goes-here \
  -p 3200:3000 \
  ghcr.io/swgoh-utils/swgoh-comlink:latest

If the port from the example above (3200) is already in use in your environment, you can expose the service on a different port by changing the first port number, which is the external port that the internal port maps to.

The -d option instructs docker to run as a daemon, and --restart always makes it so that the container will always start with docker / restart if it crashes.

If you are going to be running other services from the swgoh-tools projects and want them to be able to talk to each other directly on the same network, it's convenient to create a network for them such as docker network create swgoh-comlink. This way they can address each other using the service name and internal ports, such as http://swgoh-comlink:3000 rather than using the public IP address of your host, such as http://192.168.1.100:3200.

Deploying with Github to Heroku

If you do not want to use Docker to create your own applications and would like to just instantly host the service as is on Heroku you can use the following repos to help you quickly add it and get started.
SWGOH Comlink deploy to Heroku
SWGOH Stats deploy to Heroku

HMAC signing

To prevent tampering with messages, replay attacks, or unauthorized access, an implementation of hash message authentication code (HMAC) is provided. By default it is disabled, unless the environment variables SECRET_KEY and ACCESS_KEY are provided. Any clients authorized to use the service must also have a copy of these values to use to sign and send messages.

The implementation used here is similar to AWS4, but simplified the request header must include the Authorization header in the format of: HMAC-SHA256 Credential=<secret key goes here>,Signature=<message signature goes here>

The X-Date header must include the timestamp for when the message was generated in unix epoch time format.

The SHA256 hash algorithm is used, using the secret key.

The HMAC should add these attributes of the message in this order:

  • request time (the same value as in the X-Date header)
  • request method (the verb such as GET or POST)
  • request path (the API path, such as /metadata)
  • the md5 hash of the request body in hex format. If the request body is empty, use the empty string. Otherwise, hash the stringified version of the JSON request.

The hex digest of this process should be included as the Signature in the Authorization header.

For convenience, the node client stub implements a compliant version of HMAC if it is initialized with the access key and secret to use.

If there is a significant transmission delay between your client and server, or the system clock times do not match, you may need to adjust the max/min drift environment variables to allow more tolerance for the difference in timestamps so that the messages are accepted.

Documentation

Built in documentation

There is an openapi.json file and Redoc UI interface (api-docs) served from the service that can be accessed from a web browser at http://localhost:3000/ where 3000 is the port number you have it set to. They both contain the same information discussing the endpoints and schemas the api has.

Additional documentation

Detailed information about the returned data, examples of using it, and ways to parse it can be found on these wiki pages:


Endpoints

/enums

Returns an object containing all of the game data enums.
Request Method: GET


/metadata

Returns the metadata for the game.
Request Method: POST

Parameters

payload Object | Optional
The object containing request information. Contains clientSpecs.

payload.clientSpecs Object | Optional
The object containing client specifications to return the metadata for. Contains: platform, bundleId, externalVersion, internalVersion, region.

payload.clientSpecs.platform String | Optional
The device platform.

payload.clientSpecs.bundleId String | Optional
The bundle ID.

payload.clientSpecs.externalVersion String | Optional
The external version.

payload.clientSpecs.internalVersion String | Optional
The internal version.

payload.clientSpecs.region String | Optional
The region.

enums Boolean | Optional
Indicates if the data should be returned using the enum values instead of assigned integers.

Request Body Example
{
  "payload": {
    "clientSpecs": {
      "platform": "Android",
      "bundleId": "string",
      "externalVersion": "string",
      "internalVersion": "string",
      "region": "string"
    }
  },
  "enums": false
}

/localization

Returns multiple objects containing values for names and descriptions for several different languages.
Request Method: POST

Parameters

payload Object
The object containing request information. Contains id.

payload.id String
The localization version being requested. The latest localization version will always be found in the metadata endpoint in the key "latestLocalizationBundleVersion".

unzip Boolean
Indicates if you want the data should be returned as a zipped file.

enums Boolean | Optional
Indicates if the data should be returned using the enum values instead of assigned integers.

Request Body Example
{
  "payload": {
    "id": "AfjAusw7RraFX011_Kwi2Q"
  },
  "unzip": true
}

/data

Returns multiple objects containing various different game data. The data can be returned in one large request or in multiple small requests.
Request Method: POST

Parameters

payload Object
The object containing request information. Contains version, includePveUnits and requestSegment.

payload.version String
The game data version being requested. The latest game version will always be found in the metadata endpoint in the key "latestGamedataVersion".

payload.includePveUnits Boolean
Specifies whether you want the game data to include NPC unit information. Currently this option only affects the NPC information found within the units object.

payload.requestSegment Integer
Used to indicate if you want all of the data at once or to request it in pieces. For detailed information on the returned data see the Game Data page.

0 = All data, 200+ MB
1 = Partial data A, about 50 MB
2 = Partial data B, about 50 MB
3 = Partial data C, 80+ MB, 50+ MB with includePveUnits: false
4 = Partial data D, 25+ MB

enums Boolean
Indicates if the data should be returned using the enum values instead of assigned integers.

Request Body Example
{
 "payload": {
    "version": "0.28.6:benKkOetTsaMZVWKY4E-LA",
    "includePveUnits": true,
    "requestSegment": 0
 },
 "enums": false
}

/player

Returns a players profile including roster.
Request Method: POST

Parameters

payload Object
The object containing request information. Contains allyCode and/or playerId.

payload.allyCode String
The ally code of the player you want the profile of.

payload.playerId String | Optional
The player ID of the player you want the profile of.

enums Boolean
Indicates if the data should be returned using the enum values instead of assigned integers.

Request Body Example
{
  "payload": {
    "allyCode": "596966614"
  },
  "enums": false
}

/playerArena

Returns only a players arena profile. Includes Squad Arena, Fleet Arena, and Grand Arena.
Request Method: POST

Parameters

payload Object
The object containing request information. Contains allyCode and/or playerId.

payload.allyCode String
The ally code of the player you want the profile of.

payload.playerId String | Optional
The player ID of the player you want the profile of.

enums Boolean
Indicates if the data should be returned using the enum values instead of assigned integers.

Request Body Example
{
  "payload": {
    "allyCode": "596966614"
  },
  "enums": false
}

Testing

Postman

To test using Postman you will need to first have Comlink running.

Within Postman choose to create a new request. In the address bar put http://localhost:3000/ followed by the endpoint you want to test. The above endpoint documentation will indicate if you should choose POST or GET for the request. You will also find examples of the request body that you can copy and paste directly to Postman.

In Postman under the address bar select Body, then select the raw option and change the type in the dropdown menu at the end to JSON. From there you will just paste the request body directly as is into the textbox.

If the request is too large for your system to process within Postman, instead of choosing Send, click the down arrow next to it and select Send and Download. This will place it on your hard drive and you can try opening it with a different application that can handle the file size.

If you enable HMAC signing, you can use postman's pre-request scripts to implement HMAC signing and add the script below to it. Please refer to the postman documentation for how to run the pre-request script for each request in the collection so that you do not need to modify every request.

Alternatively you can just import the following two files into Postman where it everything has already been implemented for you: Collection Import, Environment Import

Use the following steps to modify it to your setup:

  1. Select the swgoh-comlink environment from the dropdown in the top right.
  2. Click the Environment Quick Look icon.
  3. Adjust the host, port, and protocol as needed for your Comlink service.
  4. If using HMAC enter your access key and secret key.
  5. Go to the swgoh-comlink Collection and select /metadata and run it. This should automatically assign localization and game version variables in the environment.
  6. You are ready to test it out.
// Pre-request Script for HMAC signing
function getPath(url) {
  var pathRegex = /.+?\:\/\/.+?(\/.+?)(?:#|\?|$)/;
  var result = url.match(pathRegex);
  return result && result.length > 1 ? result[1] : '';
}
function signPostRequest() {
  const accessKey = pm.variables.get("accessKey");
  const secretKey = pm.variables.get("secretKey");

  if (accessKey && secretKey) {
    const reqTime = `${(new Date()).getTime()}`;

    // resolve the variables in the raw request body
    const Property = require('postman-collection').Property;
    const resolvedBody = Property.replaceSubstitutions(pm.request.body.raw || "{}", pm.variables.toObject())

    const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secretKey)
    hmac.update(reqTime); // request time
    hmac.update(request['method']); // verb e.g POST
    hmac.update(getPath(request['url'])); // url e.g /authGuest

    const hash = CryptoJS.MD5(JSON.stringify(JSON.parse(resolvedBody)));
    hmac.update(hash.toString(CryptoJS.enc.Hex))
    const hmacDigest = hmac.finalize().toString(CryptoJS.enc.Hex);

    pm.request.headers.add({key: 'X-Date', value: reqTime});
    pm.request.headers.add({
      key: 'Authorization',
      value: `HMAC-SHA256 Credential=${accessKey},Signature=${hmacDigest}`
    });
  }
}
signPostRequest();
⚠️ **GitHub.com Fallback** ⚠️