1
\$\begingroup\$

Full code is here: https://github.com/LionelBergen/LeagueReddit

I'm creating a wrapper for the LeagueAPI in Node.js.

This includes methods to call various endpoints and classes to map the outputs & inputs.

'Champion' class is Integer mapped to a name. (output from endpoints)

Champion.js

const Champion =
{
    266 : 'AATROX',
    103 : 'AHRI',
    84 : 'AKALI',
    12 : 'ALISTAR'
}

module.exports = Champion;

'LeagueAccountInfo' contains data returned from an endpoint method.

LeagueAccountInfo.js

class LeagueAccountInfo
{
    constructor(id, accountId, puuid, name, profileIconId, summonerLevel)
    {
        this.id = id;
        this.accountId = accountId;
        this.puuid = puuid;
        this.name = name;
        this.profileIconId = profileIconId;
        this.summonerLevel = summonerLevel;
    }

    static from(json)
    {
        return Object.assign(new LeagueAccountInfo(), json);
    }
}

module.exports = LeagueAccountInfo;

Here is the main class:

LeagueAPI.js

require('./classes');
const https = require('https');

// Riot specifies this as a sample regexp to validate names
// any visible Unicode letter characters, digits (0-9), spaces, underscores, and periods.
const NAME_REGEXP = new RegExp('^[0-9\\p{L} _\\.]+$');

const GET_SUMMONER_BY_NAME_URL = 'https://%region%.api.riotgames.com/lol/summoner/v4/summoners/by-name/%name%?api_key=%apikey%';
const GET_CHAMPION_MASTERY_URL = 'https://%region%.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-summoner/%name%?api_key=%apikey%';
const GET_CHAMPION_ROTATION_URL = 'https://%region%.api.riotgames.com/lol/platform/v3/champion-rotations?api_key=%apikey%';
const GET_QUEUES_WITH_RANKS_URL = 'https://%region%.api.riotgames.com/lol/league/v4/positional-rank-queues?api_key=%apikey%';
const GET_STATUS_URL = 'https://%region%.api.riotgames.com/lol/status/v3/shard-data?api_key=%apikey%';
const GET_ACTIVE_GAME_URL = 'https://%region%.api.riotgames.com/lol/spectator/v4/active-games/by-summoner/%name%?api_key=%apikey%';
const GET_MATCHLIST_URL = 'https://%region%.api.riotgames.com/lol/match/v4/matchlists/by-account/%accountid%?api_key=%apikey%'
const GET_MATCH_URL = 'https://%region%.api.riotgames.com/lol/match/v4/matches/%matchid%?api_key=%apikey%';

class LeagueAPI
{
    constructor(apiKey, region)
    {
        this.apiKey = apiKey;
        this.region = region;
    }

    changeRegion(region)
    {
        this.region = region;
    }

    getStatus(callback)
    {
        makeAnHTTPSCall(getURLStatus(this.apiKey, this.region), callback);
    }

    getMatch(matchId, callback)
    {
        makeAnHTTPSCall(getMatchURL(matchId, this.apiKey, this.region), callback);
    }

    getPositionalRankQueues(callback)
    {
        makeAnHTTPSCall(getURLQueuesWithRanks(this.apiKey, this.region), callback);
    }

    getSummonerByName(summonerName, callback)
    {
        makeAnHTTPSCall(getURLSummonerByName(summonerName, this.apiKey, this.region), function(data) {
            callback(LeagueAccountInfo.from(data));
        });
    }

    getActiveGames(accountObj, callback)
    {
        let summonerId = getSummonerIdFromParam(accountObj);

        makeAnHTTPSCall(getURLActiveGames(summonerId, this.apiKey, this.region), callback);
    }

    getMatchList(accountObj, callback)
    {
        let accountId = getAccountIdFromParam(accountObj);

        makeAnHTTPSCall(getURLMatchList(accountId, this.apiKey, this.region), callback);
    }

    getChampionMastery(accountObj, callback)
    {
        let summonerId = getSummonerIdFromParam(accountObj);

        makeAnHTTPSCall(getURLChampionMastery(summonerId, this.apiKey, this.region), function(data) {
            let championMasterObjects = [];
            for (var i=0; i < data.length; i++)
            {
                championMasterObjects.push(ChampionMastery.from(data[i]));
            }
            callback(championMasterObjects);
        });
    }

    getFreeChampionRotation(callback)
    {
        makeAnHTTPSCall(getURLChampRotation(this.apiKey, this.region), function(data) { 
            callback(ChampionRotation.from(data));
        });
    }
}

function getSummonerIdFromParam(param)
{
    summonerId = '';

    if (param instanceof LeagueAccountInfo)
    {
        summonerId = param.id;
    }
    else if (param instanceof String)
    {
        summonerId = param;
    }
    else
    {
        throw 'invalid argument, requires summonerId or LeagueAccountInfo object';
    }

    return summonerId;
}

function getAccountIdFromParam(param)
{
    accountId = '';

    if (param instanceof LeagueAccountInfo)
    {
        accountId = param.accountId;
    }
    else if (param instanceof String)
    {
        accountId = param;
    }
    else
    {
        throw 'invalid argument, requires accountId or LeagueAccountInfo object';
    }

    return accountId;
}

function getMatchURL(matchId, apiKey, region)
{
    return getURLWithRegionAndAPI(GET_MATCH_URL, apiKey, region).replace('%matchid%', matchId);
}

function getURLMatchList(summonerId, apiKey, region)
{
    return getURLWithRegionAndAPI(GET_MATCHLIST_URL, apiKey, region).replace('%accountid%', summonerId);
}

function getURLActiveGames(summonerId, apiKey, region)
{
    return getURLWithRegionAndAPI(GET_ACTIVE_GAME_URL, apiKey, region).replace('%name%', summonerId);
}

function getURLStatus(apiKey, region)
{
    return getURLWithRegionAndAPI(GET_STATUS_URL, apiKey, region);
}

function getURLQueuesWithRanks(apiKey, region)
{
    return getURLWithRegionAndAPI(GET_QUEUES_WITH_RANKS_URL, apiKey, region);
}

function getURLChampionMastery(summonerName, apiKey, region)
{
    return getURLWithRegionAndAPI(GET_CHAMPION_MASTERY_URL, apiKey, region).replace('%name%', summonerName);
}

function getURLSummonerByName(summonerName, apiKey, region)
{
    return getURLWithRegionAndAPI(GET_SUMMONER_BY_NAME_URL, apiKey, region).replace('%name%', summonerName);
}

function getURLChampRotation(apiKey, region)
{
    return getURLWithRegionAndAPI(GET_CHAMPION_ROTATION_URL, apiKey, region);
}

// All endpoint URL's contain APIKey and Region
function getURLWithRegionAndAPI(url, apiKey, region)
{
    return url.replace('%apikey%', apiKey).replace('%region%', region);
}

function hasError(jsonData)
{
    return jsonData.status ? true : false;
}

function makeAnHTTPSCall(URL, callback)
{
    https.get(URL, (resp) => {
      let data = '';

      // A chunk of data has been recieved.
      resp.on('data', (chunk) => {
        data += chunk;
      });

      // The whole response has been received.
      resp.on('end', () => {
            let parsedData = JSON.parse(data);

            if (hasError(parsedData))
            {
                console.log('failed: ');
                console.log(parsedData);
            }
            else
            {
                callback(parsedData);
            }
      });

    // TODO: Errors are important, save to a database or Log file
    }).on("error", (err) => {
      console.log("Error: " + err.message);
    });
}

module.exports = LeagueAPI;

I've created a folder with all the of mapping classes in one folder, with an index.js inside of it so I could import it via require('./classes'):

LeagueAccountInfo = require('./LeagueAccountInfo.js');
ChampionMastery = require('./ChampionMastery.js');
LeagueChampions = require('./Champion.js');
Region = require('./Region.js');
ChampionRotation = require('./ChampionRotation.js');

module.exports = {LeagueAccountInfo, LeagueChampions, ChampionMastery, ChampionRotation, Region}

Note: Code is currently in a working state, but not finished. I plan to add the other endpoint methods & classes after review.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I've taken a look at other Node Code Reviews and done some research;

Use Promises instead of Callbacks. This should be an easy change, just implement a Promise in the makeAnHTTPSCallmathod and remove all callbacks.

This makes error handling easier and makes the code much easier to follow:

// Example usage after implementing Promises
LeagueAPI.getSummonerByName().then(function(data) {
    return LeagueAPI.getMatchList(data);
})
.then(console.log)
.catch(console.log);

// Example method with Promise (makeAnHTTPSCall should also use Promise instead of callback)
getChampionMasteryByChampion(accountObj, championObj)
{
    let summonerId = getSummonerIdFromParam(accountObj);
    let championId = getChampionIdFromParam(championObj);

    return new Promise(function(resolve, reject) {
        makeAnHTTPSCall(getURLChampionMasteryByChampion(summonerId, championId, this.apiKey, this.region))
        .then(function(data) {
            resolve(ChampionMastery.from(data));
        })
        .catch(reject);
    });
}

Champion was supposed to be used somewhat like an Enum. Currently for example Champion.AATROX would give an error, since '266' is the key, but Champion.266 is invalid JS and doesn't really make sense anyway. Instead I've used Objects as the values:

const Champion =
{
    AATROX: {value: 266, label: 'Aatrox' },
    AHRI: {value: 103, label: 'Ahri' },
    AKALI: {value: 84, label: 'Akali' },
    ALISTAR: {value: 12, label: 'Alistar' },
    findById(id)
    {
        return Object.values(this).find(champ => {return champ.value === id})
    }
}

Now Champion.AATROX can be used as well as Champion.findById(266).

\$\endgroup\$
1
  • \$\begingroup\$ I'm marking this as an accepted answer, but am more than willing to mark a different/better answer if someone posts one \$\endgroup\$
    – dustytrash
    Commented Apr 29, 2019 at 18:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.