summaryrefslogtreecommitdiff
path: root/api.js
blob: 58a51d7848cbed30074ce212623a76ee660ee711 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/env node
/* jshint esnext: true */
"use strict";
const Promise = require("bluebird"),
    request = require("request-promise"),
    sleep = require("sleep-promise"),
    fs = require("fs"),
    jsonapi = require("./jsonapi");

const MADGLORY_TOKEN = process.env.MADGLORY_TOKEN,
    MADGLORY_URL = process.env.MADGLORY_URL || "https://api.dc01.gamelockerapp.com",
    API_TIMEOUT = process.env.API_TIMEOUT || 20,  // seconds
    ERROR_LOG = process.env.ERROR_LOG || "./errors.json";
if (MADGLORY_TOKEN == undefined) throw "Need an API token";

const api = module.exports;

// send a request to url with options, log data about it
// return [jsonapi parsed data, jsonapi links]
module.exports.request = async (url, options, logger) => {
    let response;
    while (true) {
        try {
            const opts = {
                uri: url,
                headers: {
                    "X-Title-Id": "semc-vainglory",
                    "Authorization": MADGLORY_TOKEN
                },
                qs: options,
                json: true,
                gzip: true,
                time: true,
                forever: true,
                timeout: API_TIMEOUT*1000,
                strictSSL: true,
                resolveWithFullResponse: true
            };
            logger.info("API request", {
                uri: opts.uri,
                qs: opts.qs
            });
            response = await request(opts);
            return [jsonapi.parse(response.body), response.body.links];
        } catch (err) {
            response = err.response;
            if (err.statusCode == 429) {
                logger.warn("rate limited, sleeping");
                await sleep(100);  // no return, no break => retry
                continue;
            } else if (err.statusCode != 404) {
                logger.error("API error", {
                    uri: url,
                    qs: options,
                    status: err.statusCode,
                    error: response? response.body : err
                });
                fs.appendFileSync(ERROR_LOG, JSON.stringify(err) + "\n");
                throw err;  // rethrow for services to catch and handle
            }
            logger.warn("not found", {
                uri: url,
                qs: options,
                status: err.statusCode
            });
            return [undefined, undefined];
        } finally {
            if (response != undefined) {  // else non-requests error
                logger.info("API response", {
                    uri: url,
                    qs: options,
                    status: response.statusCode,
                    connection_start: response.timings.connect,
                    connection_first: response.timings.response,
                    connection_end: response.timings.end,
                    ratelimit_remaining: parseInt(response.headers["x-ratelimit-remaining"])
                });
            }
        }
    }
}

// loop over all pages, yield jsonapi parsed data results
// TODO make it an async generator? coroutine?
module.exports.requests = async (endpoint, region, options, logger) => {
    let url = api.url(endpoint, region),
        result = [];
    do {
        const [data, links] = await api.request(url, options, logger);
        if (data == undefined) break;  // 404
        if (links == undefined) break;  // 404
        url = links.next;
        result.push(...data);
        if (links.next == undefined) break;  // last page
    } while (true);
    return result;
}

module.exports.url = (endpoint, region) =>
    MADGLORY_URL + "/shards/" + region + "/" + endpoint;