<commit_before>'use strict';

const http = require('http');
const POST_MATCHMAKER_MARK = 'postMatchmaker';

class MatchMaker {

    constructor(expressServer, serverEngine, options) {

        this.numServers = 0;
        this.options = Object.assign({
            pollPeriod: 10000,              // milliseconds between server poll loops
            playersPerServer: 6,            // max players per server
            matchmakerPath: '/',            // path at which matchmaker is used
            domain: 'AwesomeShooter.com',   // domain name of game servers
            hostname: 'gameserver'          // hostname prefix for servers
        }, options);

        // poll servers at fixed interval
        setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);

        // create status routes
        expressServer.get('/gameStatus', (req, res) => { res.send(serverEngine.gameStatus()); });
        expressServer.get('/matchmakerStatus', (req, res) => { res.send(this.matchmakerStatus()); });
        expressServer.use('/', (req, res, next) => { this.matchMake(req, res, next); });
    }

    serverName(serverNumber) {
        const zeroPaddedNumber = (String(1e15 + serverNumber)).slice(-6);
        return `${this.options.hostname}${zeroPaddedNumber}.${this.options.domain}`;
    }

    matchmakerStatus() {
        let matchmakerStatus = {
            numServers: this.numServers,
            gameServers: {}
        };

        return JSON.stringify(matchmakerStatus);
    }

    pollNext(serverNumber) {

        try {
            http.get(this.serverName(serverNumber), (res) => {
                let data = '';
                if (res.statusCode !== 200) { throw new Error(`status=${res.statusCode}`); }
                res.on('data', function(d) { data += d; });
                res.on('end', function() {
                    this.servers[serverNumber] = JSON.parse(data);
                    this.pollNext(serverNumber + 1);
                    return;
                });
            });
        } catch (e) {
            this.pollingLoopRunning = false;
            this.numServers = serverNumber;
        }
    }

    pollGameServers() {
        if (this.pollingLoopRunning) {
            console.log('MatchMaker: previous polling loop still running');
            setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);
            return;
        }
        this.pollingLoopRunning = true;
        this.pollNext(0);
        setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);
    }

    matchMake(req, res, next) {

        // check for matchmaker path
        // also, if matchmaking already happened, let the game server running
        if (req.path !== this.options.matchmakerPath || req.query.hasOwnProperty(POST_MATCHMAKER_MARK)) {
            next();
            return;
        }

        // set the serverName
        if (!this.options.serverName) this.options.serverName = req.hostname;

        // choose an appropriate server
        for (let s = 0; s < this.numServers; s++) {
            let server = this.servers[s];
            if (server && server.numPlayers) {
                if (server.numPlayers < this.options.playersPerServer) {
                    res.redirect(`${this.serverName(s)}?${POST_MATCHMAKER_MARK}=true`);
                    return;
                }
            }
        }

        console.log('ERROR! MATCHMAKER FAILURE! game server info:');
        console.log(JSON.stringify(this.servers, null, 2));

    }

}

module.exports = MatchMaker;
<commit_msg>fix matchmaker usage of http<commit_after>'use strict';

const http = require('http');
const GAMESTATUS_PATH = '/gameStatus';
const MATCHMAKERSTATUS_PATH = '/matchmakerStatus';
const POST_MATCHMAKER_MARK = 'postMatchmaker';

class MatchMaker {

    constructor(expressServer, serverEngine, options) {

        this.numServers = 0;
        this.gameServers = {};
        this.options = Object.assign({
            pollPeriod: 10000,              // milliseconds between server poll loops
            playersPerServer: 6,            // max players per server
            matchmakerPath: '/',            // path at which matchmaker is used
            domain: 'AwesomeShooter.com',   // domain name of game servers
            hostname: 'gameserver',          // hostname prefix for servers
            verbose: false
        }, options);

        // poll servers at fixed interval
        setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);

        // create status routes
        expressServer.get(GAMESTATUS_PATH, (req, res) => { res.send(serverEngine.gameStatus()); });
        expressServer.get(MATCHMAKERSTATUS_PATH, (req, res) => { res.send(this.matchmakerStatus()); });
        expressServer.use('/', (req, res, next) => { this.matchMake(req, res, next); });
    }

    serverName(serverNumber) {
        const zeroPaddedNumber = (String(1e15 + serverNumber)).slice(-6);
        return `${this.options.hostname}${zeroPaddedNumber}.${this.options.domain}`;
    }

    matchmakerStatus() {
        let matchmakerStatus = {
            numServers: this.numServers,
            gameServers: this.gameServers
        };

        return JSON.stringify(matchmakerStatus);
    }

    pollEnd(serverNumber) {
        this.pollingLoopRunning = false;
        this.numServers = serverNumber;
        if (this.options.verbose)
            console.log(`total number of servers ${serverNumber}`);
    }

    pollNext(serverNumber) {

        const serverURL = `http://${this.serverName(serverNumber)}${GAMESTATUS_PATH}`;
        let that = this;
        try {
            console.log(`checking server ${serverURL}`);
            http.get(serverURL, (res) => {
                let data = '';
                if (res.statusCode !== 200) {
                    that.pollEnd(serverNumber);
                    return;
                }
                res.on('data', function(d) { data += d; });
                res.on('end', function() {
                    if (that.options.verbose)
                        console.log(`using server ${serverNumber} data = ${data}`);
                    that.gameServers[serverNumber] = JSON.parse(data);
                    that.pollNext(serverNumber + 1);
                    return;
                });
            });
        } catch (e) { that.pollEnd(serverNumber); }
    }

    pollGameServers() {
        if (this.pollingLoopRunning) {
            console.log('MatchMaker: previous polling loop still running');
            setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);
            return;
        }
        this.pollingLoopRunning = true;
        this.pollNext(0);
        setTimeout(this.pollGameServers.bind(this), this.options.pollPeriod);
    }

    matchMake(req, res, next) {

        // check for matchmaker path
        // also, if matchmaking already happened, let the game server running
        if (req.path !== this.options.matchmakerPath || req.query.hasOwnProperty(POST_MATCHMAKER_MARK)) {
            next();
            return;
        }

        // set the serverName
        if (!this.options.serverName) this.options.serverName = req.hostname;

        // choose an appropriate server
        for (let s = 0; s < this.numServers; s++) {
            let server = this.gameServers[s];
            if (server && server.hasOwnProperty('numPlayers')) {
                if (server.numPlayers < this.options.playersPerServer) {
                    const redirectURL = `http://${this.serverName(s)}?${POST_MATCHMAKER_MARK}=true`;
                    if (this.options.verbose)
                        console.log(`======> player redirected to server ${s} at [${redirectURL}]`);
                    res.redirect(redirectURL);
                    return;
                }
            }
        }

        console.log('ERROR! MATCHMAKER FAILURE! game server info:');
        console.log(JSON.stringify(this.gameServers, null, 2));

    }

}

module.exports = MatchMaker;
<|endoftext|>


<commit_before>var _ = require('lodash');

module.exports = function(robot) {
    robot.hear(/\(tableflip\)/i, function(res) {
    	res.reply("Please Respect Tables   ┬─┬ノ(ಠ_ಠノ)");
    });

    robot.hear(/jose/i, function (res) {
        res.send("(trolljose)");
    });

    robot.respond(/jose bomb( (\d+))?/i, function (msg) {
        var count = msg.match[2] || 5;
        for(var i = 0; i < count - 1; i++) {
            msg.send("(trolljose)");
        }
    });

    // robot.respond(/you suck/i, function(res) {
    // 	res.reply("\n\n....................../´¯/)\n....................,/¯../\n.................../..../\n............./´¯¯¯/'...'/´¯¯¯/`\n........../'/.../..../....././¨¯\\\n........('(...´...´.... ¯~/'...')\n.........\\.................'...../\n..........\\.................../\n............\\..............(\n..............\\.............\\....");
    // });

    var meanArray = [
    	'http://cdn.meme.am/instances/500x/53668178.jpg',
    	'http://cdn.meme.am/instances/400x/55364004.jpg',
    	'http://www.quickmeme.com/img/ab/abeee7d0019009857f3f87789e1913bad8da23b29881d920c5c8fc7038a75e7b.jpg',
    	'http://cdn.meme.am/instances/500x/52615916.jpg',
    	'http://i.qkme.me/3qf8sl.jpg',
    	'http://cdn.meme.am/instances/400x/54388732.jpg'

    ];

    robot.hear(/(you suck|you son of a bitch|damnit cubot|I hate you|(you are|you're) (the worst|terrible))/i, function(res) {
    	res.send(res.random(meanArray));
    });

    robot.hear(/testing 123/i, function(msg) {
        var roomId = msg.envelope.user.reply_to.split('_')[0];
        msg.send('/code ' + roomId);
    });
};<commit_msg>Hmm didn't work<commit_after>var _ = require('lodash');

module.exports = function(robot) {
    robot.hear(/\(tableflip\)/i, function(res) {
    	res.reply("Please Respect Tables   ┬─┬ノ(ಠ_ಠノ)");
    });

    robot.hear(/jose/i, function (res) {
        res.send("(trolljose)");
    });

    robot.respond(/jose bomb( (\d+))?/i, function (msg) {
        var count = msg.match[2] || 5;
        for(var i = 0; i < count - 1; i++) {
            msg.send("(trolljose)");
        }
    });

    // robot.respond(/you suck/i, function(res) {
    // 	res.reply("\n\n....................../´¯/)\n....................,/¯../\n.................../..../\n............./´¯¯¯/'...'/´¯¯¯/`\n........../'/.../..../....././¨¯\\\n........('(...´...´.... ¯~/'...')\n.........\\.................'...../\n..........\\.................../\n............\\..............(\n..............\\.............\\....");
    // });

    var meanArray = [
    	'http://cdn.meme.am/instances/500x/53668178.jpg',
    	'http://cdn.meme.am/instances/400x/55364004.jpg',
    	'http://www.quickmeme.com/img/ab/abeee7d0019009857f3f87789e1913bad8da23b29881d920c5c8fc7038a75e7b.jpg',
    	'http://cdn.meme.am/instances/500x/52615916.jpg',
    	'http://i.qkme.me/3qf8sl.jpg',
    	'http://cdn.meme.am/instances/400x/54388732.jpg'

    ];

    robot.hear(/(you suck|you son of a bitch|damnit cubot|I hate you|(you are|you're) (the worst|terrible))/i, function(res) {
    	res.send(res.random(meanArray));
    });

    robot.hear(/testing 123/i, function(msg) {
        var roomId = msg.envelope.user.reply_to;
        msg.send('/code ' + roomId);
    });
};<|endoftext|>


<commit_before>/*
 Copyright 2013 Daniel Wirtz <dcode@dcode.io>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

/**
 * ClosureCompiler.js: Configure Script (c) 2013 Daniel Wirtz <dcode@dcode.io>
 * Released under the Apache License, Version 2.0
 * see: https://github.com/dcodeIO/ClosureCompiler.js for details
 */

var ClosureCompiler = require(__dirname+"/../ClosureCompiler.js"),
    fs = require("fs"),
    path = require("path"),
	child_process = require("child_process"),
    pkg = require(__dirname+"/../package.json"),
    zlib = require("zlib"),
    tar = require("tar");

console.log("Configuring ClosureCompiler.js "+pkg.version+" ...\n");

// Closure Compiler download url
var ccUrl = "http://dl.google.com/closure-compiler/compiler-latest.tar.gz";

// Temporary file for the download
var ccTempFile = path.normalize(path.join(__dirname, "..", "compiler", "compiler.tar.gz"));

// Gets the platform postfix for downloads
function platformPostfix() {
    if (/^win/.test(process.platform)) {
        return process.arch == 'x64' ? 'win64' : 'win32';
    } else if (/^darwin/.test(process.platform)) {
        return 'osx64';
    }
    // This might not be ideal, but we don't have anything else and there is always a chance that it will work
    return process.arch == 'x64' ? 'linux64' : 'linux32';
}

// Bundled JRE download url
var jrePrefix = "http://bundled-openjdk-jre.googlecode.com/files/OpenJDK-JRE-7u6_24-";
var jrePostfix = ".tar.gz";
var jreUrl = jrePrefix+platformPostfix()+jrePostfix;

// Temporary file for the download
var jreTempFile = path.normalize(path.join(__dirname, "..", "jre", "jre.tar.gz"));

if (!fs.existsSync) fs.existsSync = path.existsSync; // node < 0.8

console.log("  Downloading "+ccUrl+" ...");
var lastBytes = 0, currentBytes = 0, mb = 1024*1024;
download(ccUrl, ccTempFile, function(error, bytes) {
    if (error) {
        console.log("  ✖ Download failed: "+error+"\n");
        fail();
    }
    console.log("  ✔ Download complete: "+ccTempFile+" ("+parseInt(bytes/mb, 10)+" mb)\n");
    setTimeout(function() {
        console.log("  Unpacking "+ccTempFile+" ...");
        unpack(ccTempFile, function(error) {
            if (error) {
                console.log("  ✖ Unpack failed: "+error+"\n");
                fail();
            }
            setTimeout(function() { // Let the entry callbacks finish
                console.log("  ✔ Unpack complete.\n");
                configure_jre();
            }, 1000);
        }, function(entry) {
            console.log("  | "+entry["path"]);
        });
    }, 1000);
}, function(bytes, total) {
    currentBytes += bytes;
    if (currentBytes == bytes || currentBytes - lastBytes >= mb) {
        console.log("  | "+parseInt(currentBytes/mb, 10)+" / "+(total > 0 ? parseInt(total/mb, 10) : "???")+" mb");
        lastBytes = currentBytes;
    }
});

/**
 * Configures the JRE.
 */
function configure_jre() {
    console.log("  Configuring JRE ...");

    var mb = 1024*1024;

    // Test if there is already a global Java so we don't need to download anything
    ClosureCompiler.testJava(ClosureCompiler.getGlobalJava(), function(ok) {
        if (ok) {
            console.log("  ✔ Global Java is available, perfect!\n");
            // Travis CI for example has one, so we save their bandwidth. And Google's. And yours. And...
            finish();
        } else {
            if (fs.existsSync(path.join(__dirname, "..", "jre", "bin"))) {
                console.log("  ✖ Global Java not found or outdated, testing bundled JRE ...\n");
                runTest(true);
            } else {
                console.log("  ✖ Global Java not found or outdated, downloading bundled JRE ...");
                console.log("    Downloading "+jreUrl+" ...");
                lastBytes = 0; currentBytes = 0;
                download(jreUrl, jreTempFile, function(error, bytes) {
                    if (error) {
                        console.log("    ✖ Download failed: "+error+"\n");
                        fail();
                    }
                    console.log("    ✔ Download complete: "+jreTempFile+" ("+parseInt(bytes/mb, 10)+" mb)\n");
                    setTimeout(function() {
                        console.log("      Unpacking "+jreTempFile+" ...");
                        unpack(jreTempFile, function(error) {
                            if (error) {
                                console.log("      ✖ Unpack failed: "+error+"\n");
                                fail();
                            }
                            setTimeout(function() { // Let the entry callbacks finish
                                console.log("      ✔ Unpack complete.\n");
                                configure();
                                runTest(true);
                            }, 1000);
                        }, function(entry) {
                            console.log("      | "+entry["path"]);
                        });
                    }, 1000);
                }, function(bytes, total) {
                    currentBytes += bytes;
                    if (currentBytes == bytes || currentBytes - lastBytes >= mb) {
                        console.log("    | "+parseInt(currentBytes/mb, 10)+" / "+(total > 0 ? parseInt(total/mb, 10) : "???")+" mb");
                        lastBytes = currentBytes;
                    }
                });
            }
        }
    });
}

/**
 * Downloads a file.
 * @param {string} downloadUrl
 * @param {string} filename
 * @param {function(?Error,number)} callback
 * @param {function(number)=} ondata
 */
function download(downloadUrl, filename, callback, ondata) {
    var url = require("url").parse(downloadUrl);
    var out = require("fs").createWriteStream(filename, { flags: 'w', encoding: null, mode: 0666 });
    var bytes = 0, total = -1;
    var req = require(path.join(__dirname, "..", "lib", "follow-redirects.js")).http.request({
        "hostname": url["host"],
        "method": "GET",
        "path": url["path"],
        "agent": false
    }, function(res) {
        if (res.headers["content-length"]) {
            total = parseInt(res.headers["content-length"], 10);
        }
        if (res.statusCode != 200) {
            res.setEncoding(null);
            callback(new Error("Download failed: HTTP status code "+res.statusCode), -1);
            return;
        }
        res.on("data", function(chunk) {
            bytes += chunk.length;
            if (ondata) ondata(chunk.length, total);
            out.write(chunk);
        });

        res.on("end", function() {
            callback(null, bytes);
        });
    });
    req.on("error", function(e) {
        callback(e, -1);
    });
    req.end();
}

/**
 * Unpacks a file in place.
 * @param {string} filename File name
 * @param {function(?Error)} callback
 * @param {function(Object)=} entryCallback
 */
function unpack(filename, callback, entryCallback) {
    var input = fs.createReadStream(filename, { flags: 'r', encoding: null }),
        files = {},
        dir = path.dirname(filename),
        returned = false,
        to = null;

    // Finishs the unpack if all files are done
    function maybeFinish() {
        if (to !== null) clearTimeout(to);
        to = setTimeout(function() {
            var alldone = true;
            var names = Object.keys(files);
            for (var i=0; i<names.length; i++) {
                if (!files[names[i]]["done"]) {
                    alldone = false;
                    break;
                }
            }
            if (alldone && !returned) {
                returned = true;
                callback(null);
            }
        }, 1000);
    }

    input.pipe(zlib.createGunzip()).pipe(tar.Parse()).on("entry", function(entry) {
        if (entryCallback) entryCallback(entry);
        if (entry["type"] == 'File') {
            files[entry["path"]] = fs.createWriteStream(path.join(dir, entry["path"]), { flags: 'w', encoding: null });
            entry.pipe(files[entry["path"]]);
            entry.on("end", function() {
                files[entry["path"]].end();
                files[entry["path"]]["done"] = true;
                maybeFinish();
            });
        } else if (entry["type"] == "Directory") {
            try {
                fs.mkdirSync(path.join(dir, entry["path"]));
            } catch (e) {
                if (!fs.existsSync(path.join(dir, entry["path"]))) {
                    if (!returned) {
                        returned = true;
                        callback(e);
                    }
                }
            }
        }
    }).on("error", function(e) {
        if (!returned) {
            returned = true;
            callback(e);
        }
    });
}

/**
 * Configures our bundled Java.
 */
function configure() {
    var java = path.normalize(path.join(__dirname, "..", "jre", "bin", "java"+ClosureCompiler.JAVA_EXT));
    console.log("  Configuring bundled JRE for platform '"+platformPostfix()+"' ...");
    if (!/^win/.test(process.platform)) {
        var jre = path.normalize(path.join(__dirname, "..", "jre"));
        console.log("  | 0755 "+jre);
        fs.chmodSync(jre, 0755);
        console.log("  | 0755 "+path.join(jre, "bin"));
        fs.chmodSync(path.join(jre, "bin"), 0755);
        console.log("  | 0755 "+java);
        fs.chmodSync(java, 0755);
        console.log("  Complete.\n");
    } else {
        console.log("  Complete (not necessary).\n");
    }
}

/**
 * Runs the final test.
 * @param {boolean=} ensureBundled
 */
function runTest(ensureBundled) {
    console.log("  Testing bundled Java ...");
    console.log("  | exec "+ClosureCompiler.getBundledJava());
    ClosureCompiler.testJava(ClosureCompiler.getBundledJava(), function(ok, err) {
        if (ok) {
            console.log("  ✔ Successfully called bundled Java!\n");
            finish();
        } else {
            if (ensureBundled) {
                console.log("  ✖ Failed to call bundled java:");
                console.log("-----\n"+err+"\n-----");
                console.log("  ✖ Trying global java instead ...");
            } else {
                console.log("  ✖ Failed to call bundled java, trying global java instead ...");
            }
            console.log("    | exec "+ClosureCompiler.getGlobalJava()+"\n");
            ClosureCompiler.testJava(ClosureCompiler.getGlobalJava(), function(ok, err) {
                if (ok) {
                    console.log("    ✔ Successfully called global Java!\n");
                    finish();
                } else {
                    console.log("    ✖ Failed to call global Java.\n");
                    fail();
                }
            });
        }
    });
}

/**
 * Cleans up.
 */
function cleanUp() {
    try { fs.unlinkSync(ccTempFile); } catch (e) {}
    try { fs.unlinkSync(jreTempFile); } catch (e) {}
    // ...your harddrive's space.
}

/**
 * Fails.
 */
function fail() {
    cleanUp();
    console.log("  ✖ Unfortunately, ClosureCompiler.js could not be configured.");
    console.log("    See: https://github.com/dcodeIO/ClosureCompiler.js (create an issue maybe)\n");
    process.exit(1);
}

/**
 * Finishes.
 */
function finish() {
    cleanUp();
    console.log("  ✔ ClosureCompiler.js has successfully been configured!\n");
}
<commit_msg>Configure downloads behind proxies<commit_after>/*
 Copyright 2013 Daniel Wirtz <dcode@dcode.io>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

/**
 * ClosureCompiler.js: Configure Script (c) 2013 Daniel Wirtz <dcode@dcode.io>
 * Released under the Apache License, Version 2.0
 * see: https://github.com/dcodeIO/ClosureCompiler.js for details
 */

var ClosureCompiler = require(__dirname+"/../ClosureCompiler.js"),
    fs = require("fs"),
    path = require("path"),
	child_process = require("child_process"),
    pkg = require(__dirname+"/../package.json"),
    zlib = require("zlib"),
    tar = require("tar");

console.log("Configuring ClosureCompiler.js "+pkg.version+" ...\n");

// Closure Compiler download url
var ccUrl = "http://dl.google.com/closure-compiler/compiler-latest.tar.gz";

// Temporary file for the download
var ccTempFile = path.normalize(path.join(__dirname, "..", "compiler", "compiler.tar.gz"));

// Gets the platform postfix for downloads
function platformPostfix() {
    if (/^win/.test(process.platform)) {
        return process.arch == 'x64' ? 'win64' : 'win32';
    } else if (/^darwin/.test(process.platform)) {
        return 'osx64';
    }
    // This might not be ideal, but we don't have anything else and there is always a chance that it will work
    return process.arch == 'x64' ? 'linux64' : 'linux32';
}

// Bundled JRE download url
var jrePrefix = "http://bundled-openjdk-jre.googlecode.com/files/OpenJDK-JRE-7u6_24-";
var jrePostfix = ".tar.gz";
var jreUrl = jrePrefix+platformPostfix()+jrePostfix;

// Temporary file for the download
var jreTempFile = path.normalize(path.join(__dirname, "..", "jre", "jre.tar.gz"));

if (!fs.existsSync) fs.existsSync = path.existsSync; // node < 0.8

console.log("  Downloading "+ccUrl+" ...");
var lastBytes = 0, currentBytes = 0, mb = 1024*1024;
download(ccUrl, ccTempFile, function(error, bytes) {
    if (error) {
        console.log("  ✖ Download failed: "+error+"\n");
        fail();
    }
    console.log("  ✔ Download complete: "+ccTempFile+" ("+parseInt(bytes/mb, 10)+" mb)\n");
    setTimeout(function() {
        console.log("  Unpacking "+ccTempFile+" ...");
        unpack(ccTempFile, function(error) {
            if (error) {
                console.log("  ✖ Unpack failed: "+error+"\n");
                fail();
            }
            setTimeout(function() { // Let the entry callbacks finish
                console.log("  ✔ Unpack complete.\n");
                configure_jre();
            }, 1000);
        }, function(entry) {
            console.log("  | "+entry["path"]);
        });
    }, 1000);
}, function(bytes, total) {
    currentBytes += bytes;
    if (currentBytes == bytes || currentBytes - lastBytes >= mb) {
        console.log("  | "+parseInt(currentBytes/mb, 10)+" / "+(total > 0 ? parseInt(total/mb, 10) : "???")+" mb");
        lastBytes = currentBytes;
    }
});

/**
 * Configures the JRE.
 */
function configure_jre() {
    console.log("  Configuring JRE ...");

    var mb = 1024*1024;

    // Test if there is already a global Java so we don't need to download anything
    ClosureCompiler.testJava(ClosureCompiler.getGlobalJava(), function(ok) {
        if (ok) {
            console.log("  ✔ Global Java is available, perfect!\n");
            // Travis CI for example has one, so we save their bandwidth. And Google's. And yours. And...
            finish();
        } else {
            if (fs.existsSync(path.join(__dirname, "..", "jre", "bin"))) {
                console.log("  ✖ Global Java not found or outdated, testing bundled JRE ...\n");
                runTest(true);
            } else {
                console.log("  ✖ Global Java not found or outdated, downloading bundled JRE ...");
                console.log("    Downloading "+jreUrl+" ...");
                lastBytes = 0; currentBytes = 0;
                download(jreUrl, jreTempFile, function(error, bytes) {
                    if (error) {
                        console.log("    ✖ Download failed: "+error+"\n");
                        fail();
                    }
                    console.log("    ✔ Download complete: "+jreTempFile+" ("+parseInt(bytes/mb, 10)+" mb)\n");
                    setTimeout(function() {
                        console.log("      Unpacking "+jreTempFile+" ...");
                        unpack(jreTempFile, function(error) {
                            if (error) {
                                console.log("      ✖ Unpack failed: "+error+"\n");
                                fail();
                            }
                            setTimeout(function() { // Let the entry callbacks finish
                                console.log("      ✔ Unpack complete.\n");
                                configure();
                                runTest(true);
                            }, 1000);
                        }, function(entry) {
                            console.log("      | "+entry["path"]);
                        });
                    }, 1000);
                }, function(bytes, total) {
                    currentBytes += bytes;
                    if (currentBytes == bytes || currentBytes - lastBytes >= mb) {
                        console.log("    | "+parseInt(currentBytes/mb, 10)+" / "+(total > 0 ? parseInt(total/mb, 10) : "???")+" mb");
                        lastBytes = currentBytes;
                    }
                });
            }
        }
    });
}

/**
 * Downloads a file.
 * @param {string} downloadUrl
 * @param {string} filename
 * @param {function(?Error,number)} callback
 * @param {function(number)=} ondata
 */
function download(downloadUrl, filename, callback, ondata) {
    var url = require("url").parse(downloadUrl);
    var out = require("fs").createWriteStream(filename, { flags: 'w', encoding: null, mode: 0666 });
    var bytes = 0, total = -1;
    var options = {
        "hostname" : url["host"],
        "method" : "GET",
        "path" : url["path"],
        "agent" : false
    }

    // Reconfigure request options if an HTTP proxy setting exists
    if (process.env.http_proxy) {
        var http_proxy = require("url").parse(process.env.http_proxy);
        options["hostname"] = http_proxy["hostname"];
        options["port"] = http_proxy["port"];
        options["path"] = url["href"];
        options["headers"] = {
            "Host" : url["host"]
        };
    }

    var req = require(path.join(__dirname, "..", "lib", "follow-redirects.js")).http.request(options,
                                                                                function(res) {
        if (res.headers["content-length"]) {
            total = parseInt(res.headers["content-length"], 10);
        }
        if (res.statusCode != 200) {
            res.setEncoding(null);
            callback(new Error("Download failed: HTTP status code "+res.statusCode), -1);
            return;
        }
        res.on("data", function(chunk) {
            bytes += chunk.length;
            if (ondata) ondata(chunk.length, total);
            out.write(chunk);
        });

        res.on("end", function() {
            callback(null, bytes);
        });
    });
    req.on("error", function(e) {
        callback(e, -1);
    });
    req.end();
}

/**
 * Unpacks a file in place.
 * @param {string} filename File name
 * @param {function(?Error)} callback
 * @param {function(Object)=} entryCallback
 */
function unpack(filename, callback, entryCallback) {
    var input = fs.createReadStream(filename, { flags: 'r', encoding: null }),
        files = {},
        dir = path.dirname(filename),
        returned = false,
        to = null;

    // Finishs the unpack if all files are done
    function maybeFinish() {
        if (to !== null) clearTimeout(to);
        to = setTimeout(function() {
            var alldone = true;
            var names = Object.keys(files);
            for (var i=0; i<names.length; i++) {
                if (!files[names[i]]["done"]) {
                    alldone = false;
                    break;
                }
            }
            if (alldone && !returned) {
                returned = true;
                callback(null);
            }
        }, 1000);
    }

    input.pipe(zlib.createGunzip()).pipe(tar.Parse()).on("entry", function(entry) {
        if (entryCallback) entryCallback(entry);
        if (entry["type"] == 'File') {
            files[entry["path"]] = fs.createWriteStream(path.join(dir, entry["path"]), { flags: 'w', encoding: null });
            entry.pipe(files[entry["path"]]);
            entry.on("end", function() {
                files[entry["path"]].end();
                files[entry["path"]]["done"] = true;
                maybeFinish();
            });
        } else if (entry["type"] == "Directory") {
            try {
                fs.mkdirSync(path.join(dir, entry["path"]));
            } catch (e) {
                if (!fs.existsSync(path.join(dir, entry["path"]))) {
                    if (!returned) {
                        returned = true;
                        callback(e);
                    }
                }
            }
        }
    }).on("error", function(e) {
        if (!returned) {
            returned = true;
            callback(e);
        }
    });
}

/**
 * Configures our bundled Java.
 */
function configure() {
    var java = path.normalize(path.join(__dirname, "..", "jre", "bin", "java"+ClosureCompiler.JAVA_EXT));
    console.log("  Configuring bundled JRE for platform '"+platformPostfix()+"' ...");
    if (!/^win/.test(process.platform)) {
        var jre = path.normalize(path.join(__dirname, "..", "jre"));
        console.log("  | 0755 "+jre);
        fs.chmodSync(jre, 0755);
        console.log("  | 0755 "+path.join(jre, "bin"));
        fs.chmodSync(path.join(jre, "bin"), 0755);
        console.log("  | 0755 "+java);
        fs.chmodSync(java, 0755);
        console.log("  Complete.\n");
    } else {
        console.log("  Complete (not necessary).\n");
    }
}

/**
 * Runs the final test.
 * @param {boolean=} ensureBundled
 */
function runTest(ensureBundled) {
    console.log("  Testing bundled Java ...");
    console.log("  | exec "+ClosureCompiler.getBundledJava());
    ClosureCompiler.testJava(ClosureCompiler.getBundledJava(), function(ok, err) {
        if (ok) {
            console.log("  ✔ Successfully called bundled Java!\n");
            finish();
        } else {
            if (ensureBundled) {
                console.log("  ✖ Failed to call bundled java:");
                console.log("-----\n"+err+"\n-----");
                console.log("  ✖ Trying global java instead ...");
            } else {
                console.log("  ✖ Failed to call bundled java, trying global java instead ...");
            }
            console.log("    | exec "+ClosureCompiler.getGlobalJava()+"\n");
            ClosureCompiler.testJava(ClosureCompiler.getGlobalJava(), function(ok, err) {
                if (ok) {
                    console.log("    ✔ Successfully called global Java!\n");
                    finish();
                } else {
                    console.log("    ✖ Failed to call global Java.\n");
                    fail();
                }
            });
        }
    });
}

/**
 * Cleans up.
 */
function cleanUp() {
    try { fs.unlinkSync(ccTempFile); } catch (e) {}
    try { fs.unlinkSync(jreTempFile); } catch (e) {}
    // ...your harddrive's space.
}

/**
 * Fails.
 */
function fail() {
    cleanUp();
    console.log("  ✖ Unfortunately, ClosureCompiler.js could not be configured.");
    console.log("    See: https://github.com/dcodeIO/ClosureCompiler.js (create an issue maybe)\n");
    process.exit(1);
}

/**
 * Finishes.
 */
function finish() {
    cleanUp();
    console.log("  ✔ ClosureCompiler.js has successfully been configured!\n");
}
<|endoftext|>


<commit_before>import wdk from "wikidata-sdk/dist/wikidata-sdk.js";
import _ from "lodash";
import md5 from "blueimp-md5";

const apiCaller = store => next => action => {
  // eslint-disable-line

  switch (action.type) {
    case "GET_ROUTE": {
      getRoute(action, next);
      break;
    }

    case "GET_PLACE_INFO": {
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description: _.get(action, "feature.properties.type"),
          label: _.get(action, "feature.properties.name"),
          image: { thumb: "", full: "" }
        }
      });
      if (action.feature.properties.wikidata) {
        getWikidataPlaceInfo(action, next);
        break;
      } else {
        getMapillaryPlaceInfo(action, next);
        break;
      }
    }

    case "GET_REVERSE_GEOCODE": {
      getReverseGeocode(action, next);
      break;
    }

    default:
      next(action); // let through as default
      break;
  }

  return;
};

export default apiCaller;

function getRoute(action, next) {
  next({
    type: "SET_STATE_VALUE",
    key: "routeStatus",
    value: "pending"
  });

  const baseUrl = "https://api.mapbox.com/directions/v5/mapbox/";

  let profile = "driving-traffic"; // default
  let annotationsParam = "";
  if (action.modality === "car") {
    profile = "driving-traffic";
    annotationsParam = "&annotations=congestion";
  }
  if (action.modality === "bike") profile = "cycling";
  if (action.modality === "walk") profile = "walking";

  const fromCoordinates = action.directionsFrom.geometry.coordinates.join(",");
  const toCoordinates = action.directionsTo.geometry.coordinates.join(",");

  const url =
    baseUrl +
    profile +
    "/" +
    fromCoordinates +
    ";" +
    toCoordinates +
    "?access_token=" +
    action.accessToken +
    "&overview=full" +
    annotationsParam;

  // Fetch
  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      if (data.code !== "Ok") return Promise.reject();
      else {
        // Success
        next({
          type: "SET_ROUTE",
          data: data
        });
        next({
          type: "SET_STATE_VALUE",
          key: "routeStatus",
          value: "idle"
        });
        next({
          type: "TRIGGER_MAP_UPDATE",
          latestMapRepan: Date.now()
        });

        return Promise.resolve();
      }
    })
    .catch(() =>
      next({
        type: "SET_STATE_VALUE",
        key: "routeStatus",
        value: "error"
      })
    );
}

function getWikidataPlaceInfo(action, next) {
  const id = action.feature.properties.wikidata;
  const url = wdk.getEntities({
    ids: id,
    languages: ["en"]
  });

  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      // Success
      const entity = data.entities[id];
      const claims = wdk.simplifyClaims(entity.claims);
      const description = entity.descriptions.en.value;
      const label = entity.labels.en.value;
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description,
          label,
          image: getWikiMediaImageUrl(_.get(claims, "P18[0]")),
          phoneNumber: _.get(claims, "P1329[0]"),
          website: _.get(claims, "P856[0]"),
          address: _.get(claims, "P969[0]"),
          wikidata: id
        }
      });
    })
    .catch(() => {});
}

async function getMapillaryPlaceInfo(action, next) {
  const coordinates = action.feature.geometry.coordinates;

  const url = `https://a.mapillary.com/v3/images?client_id=${
    process.env.REACT_APP_MAPILLARY_CLIENT_ID
  }&closeto=${coordinates.join(",")}&lookat=${coordinates.join(
    ","
  )}&pano=false&per_page=5`;

  try {
    const res = await fetch(url, { method: "get" });
    if (res.ok) {
      const fc = await res.json();
      let key = fc.features[0].properties.key;
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description: action.feature.properties.type,
          label: action.feature.properties.name,
          image: {
            thumb: `https://images.mapillary.com/${key}/thumb-640.jpg`,
            full: `https://images.mapillary.com/${key}/thumb-1024.jpg`
          }
        }
      });
    } else throw new Error(res.statusText);
  } catch (error) {}
}

function getReverseGeocode(action, next) {
  if (!action.coordinates) {
    return;
  }

  const url =
    "https://api.mapbox.com/geocoding/v5/mapbox.places/" +
    action.coordinates.join(",") +
    ".json" +
    "?access_token=" +
    action.accessToken;

  next({
    type: "SET_STATE_VALUE",
    key: action.key,
    value: {
      place_name: "__loading",
      geometry: {
        type: "Point",
        coordinates: action.coordinates
      },
      properties: {}
    }
  });

  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      // Success
      if (data.features && data.features.length > 0) {
        console.log(data.features[0]);
        return next({
          type: "SET_STATE_VALUE",
          key: action.key,
          value: {
            place_name: data.features[0].place_name,
            geometry: {
              type: "Point",
              coordinates: action.coordinates
            },
            type: "Feature",
            properties: {
              name: data.features[0].text || data.features[0].place_name
            }
          }
        });
      } else return Promise.reject();
    })
    .catch(() => {
      next({
        type: "SET_STATE_VALUE",
        key: action.key,
        value: {
          place_name: "Dropped pin",
          geometry: {
            type: "Point",
            coordinates: action.coordinates
          },
          properties: {}
        }
      });
    });
}

function getWikiMediaImageUrl(claim) {
  // see https://commons.wikimedia.org/wiki/Commons:FAQ for how images are stored
  if (claim) {
    const imageName = claim.replace(/ /g, "_");
    const baseUrl = "https://upload.wikimedia.org/wikipedia/commons/";
    const hash = md5(imageName);
    const thumb =
      baseUrl +
      "thumb/" +
      hash[0] +
      "/" +
      hash.slice(0, 2) +
      "/" +
      imageName +
      "/640px-" +
      imageName;
    const full = baseUrl + hash[0] + "/" + hash.slice(0, 2) + "/" + imageName;
    return {
      thumb,
      full
    };
  } else
    return {
      thumb: "",
      full: ""
    };
}
<commit_msg>fix inexistant route update<commit_after>import wdk from "wikidata-sdk/dist/wikidata-sdk.js";
import _ from "lodash";
import md5 from "blueimp-md5";

const apiCaller = store => next => action => {
  // eslint-disable-line

  switch (action.type) {
    case "GET_ROUTE": {
      getRoute(action, next);
      break;
    }

    case "GET_PLACE_INFO": {
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description: _.get(action, "feature.properties.type"),
          label: _.get(action, "feature.properties.name"),
          image: { thumb: "", full: "" }
        }
      });
      if (action.feature.properties.wikidata) {
        getWikidataPlaceInfo(action, next);
        break;
      } else {
        getMapillaryPlaceInfo(action, next);
        break;
      }
    }

    case "GET_REVERSE_GEOCODE": {
      getReverseGeocode(action, next);
      break;
    }

    default:
      next(action); // let through as default
      break;
  }

  return;
};

export default apiCaller;

function getRoute(action, next) {
  next({
    type: "SET_STATE_VALUE",
    key: "routeStatus",
    value: "pending"
  });

  const baseUrl = "https://api.mapbox.com/directions/v5/mapbox/";

  let profile = "driving-traffic"; // default
  let annotationsParam = "";
  if (action.modality === "car") {
    profile = "driving-traffic";
    annotationsParam = "&annotations=congestion";
  }
  if (action.modality === "bike") profile = "cycling";
  if (action.modality === "walk") profile = "walking";

  const fromCoordinates = action.directionsFrom.geometry.coordinates.join(",");
  const toCoordinates = action.directionsTo.geometry.coordinates.join(",");

  const url =
    baseUrl +
    profile +
    "/" +
    fromCoordinates +
    ";" +
    toCoordinates +
    "?access_token=" +
    action.accessToken +
    "&overview=full" +
    annotationsParam;

  // Fetch
  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      if (data.code !== "Ok") return Promise.reject();
      else {
        // Success
        next({
          type: "SET_ROUTE",
          data: data
        });
        next({
          type: "SET_STATE_VALUE",
          key: "routeStatus",
          value: "idle"
        });
        next({
          type: "TRIGGER_MAP_UPDATE",
          latestMapUpdate: Date.now()
        });

        return Promise.resolve();
      }
    })
    .catch(() =>
      next({
        type: "SET_STATE_VALUE",
        key: "routeStatus",
        value: "error"
      })
    );
}

function getWikidataPlaceInfo(action, next) {
  const id = action.feature.properties.wikidata;
  const url = wdk.getEntities({
    ids: id,
    languages: ["en"]
  });

  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      // Success
      const entity = data.entities[id];
      const claims = wdk.simplifyClaims(entity.claims);
      const description = entity.descriptions.en.value;
      const label = entity.labels.en.value;
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description,
          label,
          image: getWikiMediaImageUrl(_.get(claims, "P18[0]")),
          phoneNumber: _.get(claims, "P1329[0]"),
          website: _.get(claims, "P856[0]"),
          address: _.get(claims, "P969[0]"),
          wikidata: id
        }
      });
    })
    .catch(() => {});
}

async function getMapillaryPlaceInfo(action, next) {
  const coordinates = action.feature.geometry.coordinates;

  const url = `https://a.mapillary.com/v3/images?client_id=${
    process.env.REACT_APP_MAPILLARY_CLIENT_ID
  }&closeto=${coordinates.join(",")}&lookat=${coordinates.join(
    ","
  )}&pano=false&per_page=5`;

  try {
    const res = await fetch(url, { method: "get" });
    if (res.ok) {
      const fc = await res.json();
      let key = fc.features[0].properties.key;
      next({
        type: "SET_STATE_VALUE",
        key: "placeInfo",
        value: {
          description: action.feature.properties.type,
          label: action.feature.properties.name,
          image: {
            thumb: `https://images.mapillary.com/${key}/thumb-640.jpg`,
            full: `https://images.mapillary.com/${key}/thumb-1024.jpg`
          }
        }
      });
    } else throw new Error(res.statusText);
  } catch (error) {}
}

function getReverseGeocode(action, next) {
  if (!action.coordinates) {
    return;
  }

  const url =
    "https://api.mapbox.com/geocoding/v5/mapbox.places/" +
    action.coordinates.join(",") +
    ".json" +
    "?access_token=" +
    action.accessToken;

  next({
    type: "SET_STATE_VALUE",
    key: action.key,
    value: {
      place_name: "__loading",
      geometry: {
        type: "Point",
        coordinates: action.coordinates
      },
      properties: {}
    }
  });

  fetch(url, { method: "get" })
    .then(res => {
      if (res.ok) {
        return res.json();
      } else {
        // 4xx or 5xx response
        var err = new Error(res.statusText);
        return Promise.reject(err);
      }
    })
    .then(data => {
      // Success
      if (data.features && data.features.length > 0) {
        return next({
          type: "SET_STATE_VALUE",
          key: action.key,
          value: {
            place_name: data.features[0].place_name,
            geometry: {
              type: "Point",
              coordinates: action.coordinates
            },
            type: "Feature",
            properties: {
              name: data.features[0].text || data.features[0].place_name
            }
          }
        });
      } else return Promise.reject();
    })
    .catch(() => {
      next({
        type: "SET_STATE_VALUE",
        key: action.key,
        value: {
          place_name: "Dropped pin",
          geometry: {
            type: "Point",
            coordinates: action.coordinates
          },
          properties: {}
        }
      });
    });
}

function getWikiMediaImageUrl(claim) {
  // see https://commons.wikimedia.org/wiki/Commons:FAQ for how images are stored
  if (claim) {
    const imageName = claim.replace(/ /g, "_");
    const baseUrl = "https://upload.wikimedia.org/wikipedia/commons/";
    const hash = md5(imageName);
    const thumb =
      baseUrl +
      "thumb/" +
      hash[0] +
      "/" +
      hash.slice(0, 2) +
      "/" +
      imageName +
      "/640px-" +
      imageName;
    const full = baseUrl + hash[0] + "/" + hash.slice(0, 2) + "/" + imageName;
    return {
      thumb,
      full
    };
  } else
    return {
      thumb: "",
      full: ""
    };
}
<|endoftext|>


<commit_before>/* eslint consistent-return: 0 */

import * as superagent from 'superagent';
import * as AT from 'constants/actionTypes';
import { gitHubTokenKeyInStorage } from 'constants/config';
import { setNotification } from 'utils/notifications';
import { getApiUrl } from 'utils/url';
import { setToken, removeToken, removeEnterpriseDomain } from 'utils/login';
import { get, set } from 'lodash/fp';
import { push } from 'connected-react-router';

let API = superagent;

if (process.env.NODE_ENV === 'development') {
  // We use browser based superagent in dev in order to log to network tab instead of node streams
  // eslint-disable-next-line no-shadow, no-unused-vars
  API = require('superagent/superagent');
}
const getToken = localStorage.getItem(gitHubTokenKeyInStorage);

const _headers = (additional) => ({
  'Content-Type': 'application/json',
  Authorization: `token ${getToken}`,
  ...additional
});

const gitHubAPIMiddleware = ({ dispatch }) => {
  return (next) => (action) => {
    const errorHandler = (error, result) => {
      if (error) {
        if (error.response && error.response.headers['x-github-otp']) {
          setNotification({
            title: 'Two factor authentication',
            body: `${error.response.body.message} (${error.status})`,
            type: 'info'
          });
        } else {
          setNotification({
            title: 'Error',
            body: `${error.response.body.message} (${error.status})`,
            type: 'error'
          });
        }

        dispatch({
          type: action.type.FAILURE,
          payload: error,
          meta: action.meta
        });
      } else if (result.statusCode > 204) {
        setNotification({
          title: 'Warning',
          body: `${result.statusText} - ${result.body.description}`,
          type: 'warn'
        });
      }
    };

    if (action.type === AT.GET_RATE_LIMIT) {
      dispatch({ type: AT.GET_RATE_LIMIT.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/rate_limit`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_RATE_LIMIT.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.LOGOUT) {
      removeToken();
      removeEnterpriseDomain();
      window.location.reload(true);
    }

    if (action.type === AT.LOGIN_BASIC) {
      dispatch({ type: AT.LOGIN_BASIC.PENDING, action });
      const tokenString = btoa(`${action.payload.user}:${action.payload.pass}`);
      let basicAuthHeader = {
        Authorization: `basic ${tokenString}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      };

      if (action.payload.twoFactorAuth) {
        basicAuthHeader = set('X-GitHub-OTP', action.payload.twoFactorAuth, basicAuthHeader);
      }

      API.post(`${getApiUrl('/api/v3')}/authorizations`)
        .set(basicAuthHeader)
        .send(JSON.stringify({
          scopes: ['gist'],
          note: 'Gisto - Snippets made simple',
          note_url: 'http://www.gistoapp.com',
          fingerprint: new Date().getTime()
        }))
        .end((error, result) => {
          errorHandler(error, result);

          // check if 2fa needed and notify the ui to show the field
          if (result.statusCode === 401 && result.header['x-github-otp']) {
            dispatch({ type: AT.LOGIN_BASIC_REQUEST_2FA });
          }

          if (!error && result) {
            const token = get('token', result.body);

            if (token) {
              setToken(token);
              dispatch({ type: AT.LOGIN_BASIC.SUCCESS, payload: result.body });
              window.location.reload(true);
            }
          }
        });
    }

    if (action.type === AT.LOGIN_WITH_TOKEN) {
      dispatch({ type: AT.LOGIN_WITH_TOKEN.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/user`)
        .set({ Authorization: `token ${action.payload.token}` })
        .end((error, result) => {
          errorHandler(error, result);
          if (result.statusCode === 200) {
            setToken(action.payload.token);

            window.location.reload(true);
          }
        });
    }

    if (action.type === AT.GET_USER) {
      dispatch({ type: AT.GET_USER.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/user`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_USER.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.GET_EMOJI) {
      dispatch({ type: AT.GET_EMOJI.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/emojis`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_EMOJI.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.GET_STARRED_SNIPPETS) {
      dispatch({ type: AT.GET_STARRED_SNIPPETS.PENDING, action });

      return API.get(`${getApiUrl('/api/v3')}/gists/starred`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          dispatch({ type: AT.GET_STARRED_SNIPPETS.SUCCESS, payload: result.body });
        });
    }

    if (action.type === AT.GET_SNIPPETS) {
      dispatch({ type: AT.GET_SNIPPETS.PENDING, action });

      const getGists = (page) => API.get(`${getApiUrl('/api/v3')}/gists?page=${page}&per_page=100`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPETS.SUCCESS,
              payload: result.body
            });
          }
          if (result.headers.link && result.headers.link.match(/next/ig)) {
            getGists(page + 1);
          }
        });

      getGists(1);
    }

    if (action.type === AT.GET_SNIPPET) {
      dispatch({ type: AT.GET_SNIPPET.PENDING, action });

      return API.get(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPET.SUCCESS,
              payload: result.body
            });
          }
        });
    }

    if (action.type === AT.SET_STAR) {
      dispatch({ type: AT.SET_STAR.PENDING, action });

      return API.put(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/star`)
        .set(_headers({ 'Content-Length': 0 }))
        .end((error, result) => {
          errorHandler(error, result);

          if (error || result.status !== 204) {
            dispatch({
              type: AT.SET_STAR.FAILURE,
              payload: error
            });
          }

          if (!error && result.status === 204) {
            dispatch({
              type: AT.SET_STAR.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.UNSET_STAR) {
      dispatch({ type: AT.UNSET_STAR.PENDING, action });

      return API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/star`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (error || result.status !== 204) {
            dispatch({
              type: AT.UNSET_STAR.FAILURE,
              payload: error
            });
          }

          if (!error && result.status === 204) {
            dispatch({
              type: AT.UNSET_STAR.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.CREATE_SNIPPET) {
      dispatch({ type: AT.CREATE_SNIPPET.PENDING, action });
      API.post(`${getApiUrl('/api/v3')}/gists`)
        .set(_headers())
        .send(JSON.stringify(action.payload))
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 201) {
            dispatch({
              type: AT.CREATE_SNIPPET.SUCCESS,
              meta: action.meta,
              payload: result.body
            });

            dispatch(push(`/snippet/${result.body.id}`));
          }
        });
    }

    if (action.type === AT.DELETE_SNIPPET) {
      dispatch({ type: AT.DELETE_SNIPPET.PENDING, action });
      API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 204) {
            dispatch({
              type: AT.DELETE_SNIPPET.SUCCESS,
              meta: action.meta
            });

            dispatch(push('/'));
          }
        });
    }

    if (action.type === AT.UPDATE_SNIPPET) {
      dispatch({ type: AT.UPDATE_SNIPPET.PENDING, action });

      API.patch(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .send(action.payload.snippet)
        .end((error, result) => {
          errorHandler(error, result);

          dispatch({
            type: AT.UPDATE_SNIPPET.SUCCESS,
            meta: action.meta,
            payload: result.body
          });
        });
    }

    if (action.type === AT.GET_SNIPPET_COMMENTS) {
      dispatch({ type: AT.GET_SNIPPET_COMMENTS.PENDING, meta: action.meta });
      API.get(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPET_COMMENTS.SUCCESS,
              payload: result.body,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.CREATE_SNIPPET_COMMENT) {
      dispatch({ type: AT.CREATE_SNIPPET_COMMENT.PENDING, action });
      API.post(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments`)
        .set(_headers())
        .send({ body: action.payload.body })
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.CREATE_SNIPPET_COMMENT.SUCCESS,
              payload: result.body,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.DELETE_COMMENT) {
      dispatch({ type: AT.DELETE_COMMENT.PENDING, action });
      API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments/${action.payload.commentId}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 204) {
            dispatch({
              type: AT.DELETE_COMMENT.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    next(action);
  };
};

export default gitHubAPIMiddleware;
<commit_msg>FIX (API): make errors more informative<commit_after>/* eslint consistent-return: 0 */

import * as superagent from 'superagent';
import * as AT from 'constants/actionTypes';
import { gitHubTokenKeyInStorage } from 'constants/config';
import { setNotification } from 'utils/notifications';
import { getApiUrl } from 'utils/url';
import { setToken, removeToken, removeEnterpriseDomain } from 'utils/login';
import { get, getOr, set } from 'lodash/fp';
import { push } from 'connected-react-router';

let API = superagent;

if (process.env.NODE_ENV === 'development') {
  // We use browser based superagent in dev in order to log to network tab instead of node streams
  // eslint-disable-next-line no-shadow, no-unused-vars
  API = require('superagent/superagent');
}
const getToken = localStorage.getItem(gitHubTokenKeyInStorage);

const _headers = (additional) => ({
  'Content-Type': 'application/json',
  Authorization: `token ${getToken}`,
  ...additional
});

const gitHubAPIMiddleware = ({ dispatch }) => {
  return (next) => (action) => {
    const errorHandler = (error, result) => {
      if (error) {
        if (error.response && error.response.headers['x-github-otp']) {
          setNotification({
            title: 'Two factor authentication',
            body: `${error.response.body.message}`,
            type: 'info'
          });
        } else {
          setNotification({
            title: 'Error',
            body: `
              ${error.response.body.message} ${getOr('', 'response.body.errors[0].message', error)}

              Error code: ${error.status}
            `,
            type: 'error'
          });
        }

        dispatch({
          type: action.type.FAILURE,
          payload: error,
          meta: action.meta
        });
      } else if (result.statusCode > 204) {
        setNotification({
          title: 'Warning',
          body: `${result.statusText} - ${result.body.description}`,
          type: 'warn'
        });
      }
    };

    if (action.type === AT.GET_RATE_LIMIT) {
      dispatch({ type: AT.GET_RATE_LIMIT.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/rate_limit`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_RATE_LIMIT.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.LOGOUT) {
      removeToken();
      removeEnterpriseDomain();
      window.location.reload(true);
    }

    if (action.type === AT.LOGIN_BASIC) {
      dispatch({ type: AT.LOGIN_BASIC.PENDING, action });
      const tokenString = btoa(`${action.payload.user}:${action.payload.pass}`);
      let basicAuthHeader = {
        Authorization: `basic ${tokenString}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      };

      if (action.payload.twoFactorAuth) {
        basicAuthHeader = set('X-GitHub-OTP', action.payload.twoFactorAuth, basicAuthHeader);
      }

      API.post(`${getApiUrl('/api/v3')}/authorizations`)
        .set(basicAuthHeader)
        .send(JSON.stringify({
          scopes: ['gist'],
          note: 'Gisto - Snippets made simple',
          note_url: 'http://www.gistoapp.com',
          fingerprint: new Date().getTime()
        }))
        .end((error, result) => {
          errorHandler(error, result);

          // check if 2fa needed and notify the ui to show the field
          if (result.statusCode === 401 && result.header['x-github-otp']) {
            dispatch({ type: AT.LOGIN_BASIC_REQUEST_2FA });
          }

          if (!error && result) {
            const token = get('token', result.body);

            if (token) {
              setToken(token);
              dispatch({ type: AT.LOGIN_BASIC.SUCCESS, payload: result.body });
              window.location.reload(true);
            }
          }
        });
    }

    if (action.type === AT.LOGIN_WITH_TOKEN) {
      dispatch({ type: AT.LOGIN_WITH_TOKEN.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/user`)
        .set({ Authorization: `token ${action.payload.token}` })
        .end((error, result) => {
          errorHandler(error, result);
          if (result.statusCode === 200) {
            setToken(action.payload.token);

            window.location.reload(true);
          }
        });
    }

    if (action.type === AT.GET_USER) {
      dispatch({ type: AT.GET_USER.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/user`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_USER.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.GET_EMOJI) {
      dispatch({ type: AT.GET_EMOJI.PENDING, action });
      API.get(`${getApiUrl('/api/v3')}/emojis`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({ type: AT.GET_EMOJI.SUCCESS, payload: result.body });
          }
        });
    }

    if (action.type === AT.GET_STARRED_SNIPPETS) {
      dispatch({ type: AT.GET_STARRED_SNIPPETS.PENDING, action });

      return API.get(`${getApiUrl('/api/v3')}/gists/starred`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          dispatch({ type: AT.GET_STARRED_SNIPPETS.SUCCESS, payload: result.body });
        });
    }

    if (action.type === AT.GET_SNIPPETS) {
      dispatch({ type: AT.GET_SNIPPETS.PENDING, action });

      const getGists = (page) => API.get(`${getApiUrl('/api/v3')}/gists?page=${page}&per_page=100`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPETS.SUCCESS,
              payload: result.body
            });
          }
          if (result.headers.link && result.headers.link.match(/next/ig)) {
            getGists(page + 1);
          }
        });

      getGists(1);
    }

    if (action.type === AT.GET_SNIPPET) {
      dispatch({ type: AT.GET_SNIPPET.PENDING, action });

      return API.get(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPET.SUCCESS,
              payload: result.body
            });
          }
        });
    }

    if (action.type === AT.SET_STAR) {
      dispatch({ type: AT.SET_STAR.PENDING, action });

      return API.put(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/star`)
        .set(_headers({ 'Content-Length': 0 }))
        .end((error, result) => {
          errorHandler(error, result);

          if (error || result.status !== 204) {
            dispatch({
              type: AT.SET_STAR.FAILURE,
              payload: error
            });
          }

          if (!error && result.status === 204) {
            dispatch({
              type: AT.SET_STAR.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.UNSET_STAR) {
      dispatch({ type: AT.UNSET_STAR.PENDING, action });

      return API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/star`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (error || result.status !== 204) {
            dispatch({
              type: AT.UNSET_STAR.FAILURE,
              payload: error
            });
          }

          if (!error && result.status === 204) {
            dispatch({
              type: AT.UNSET_STAR.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.CREATE_SNIPPET) {
      dispatch({ type: AT.CREATE_SNIPPET.PENDING, action });
      API.post(`${getApiUrl('/api/v3')}/gists`)
        .set(_headers())
        .send(JSON.stringify(action.payload))
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 201) {
            dispatch({
              type: AT.CREATE_SNIPPET.SUCCESS,
              meta: action.meta,
              payload: result.body
            });

            dispatch(push(`/snippet/${result.body.id}`));
          }
        });
    }

    if (action.type === AT.DELETE_SNIPPET) {
      dispatch({ type: AT.DELETE_SNIPPET.PENDING, action });
      API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 204) {
            dispatch({
              type: AT.DELETE_SNIPPET.SUCCESS,
              meta: action.meta
            });

            dispatch(push('/'));
          }
        });
    }

    if (action.type === AT.UPDATE_SNIPPET) {
      dispatch({ type: AT.UPDATE_SNIPPET.PENDING, action });

      API.patch(`${getApiUrl('/api/v3')}/gists/${action.payload.id}`)
        .set(_headers())
        .send(action.payload.snippet)
        .end((error, result) => {
          errorHandler(error, result);

          dispatch({
            type: AT.UPDATE_SNIPPET.SUCCESS,
            meta: action.meta,
            payload: result.body
          });
        });
    }

    if (action.type === AT.GET_SNIPPET_COMMENTS) {
      dispatch({ type: AT.GET_SNIPPET_COMMENTS.PENDING, meta: action.meta });
      API.get(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.GET_SNIPPET_COMMENTS.SUCCESS,
              payload: result.body,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.CREATE_SNIPPET_COMMENT) {
      dispatch({ type: AT.CREATE_SNIPPET_COMMENT.PENDING, action });
      API.post(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments`)
        .set(_headers())
        .send({ body: action.payload.body })
        .end((error, result) => {
          errorHandler(error, result);

          if (!error && result) {
            dispatch({
              type: AT.CREATE_SNIPPET_COMMENT.SUCCESS,
              payload: result.body,
              meta: action.meta
            });
          }
        });
    }

    if (action.type === AT.DELETE_COMMENT) {
      dispatch({ type: AT.DELETE_COMMENT.PENDING, action });
      API.delete(`${getApiUrl('/api/v3')}/gists/${action.payload.id}/comments/${action.payload.commentId}`)
        .set(_headers())
        .end((error, result) => {
          errorHandler(error, result);

          if (result.statusCode === 204) {
            dispatch({
              type: AT.DELETE_COMMENT.SUCCESS,
              meta: action.meta
            });
          }
        });
    }

    next(action);
  };
};

export default gitHubAPIMiddleware;
<|endoftext|>


<commit_before>$(function(){

    // Create the manage table
    initialize_manage_table("#manage-table", true);

    // Create the context menu hide callbacks
    $(window).on("mousedown resize", hide_context_menu);
    $("#main-section").on("scroll", hide_context_menu);

    // Create the context menu button callbacks
    $("#preview-button").mousedown(preview);
    $("#edit-name-button").mousedown(edit_name);
    $("#edit-class-button").mousedown(edit_class);
    $("#delete-button").mousedown(delete_document);
    $("#merge-selected-button").mousedown(merge_selected);
    $("#edit-selected-classes-button").mousedown(edit_selected_classes);
    $("#delete-selected-button").mousedown(delete_selected);
    $("#select-all-button").mousedown(manage_table_select_all);
    $("#deselect-all-button").mousedown(manage_table_deselect_all);

    // Initialize the walkthrough
    initialize_walkthrough(walkthrough);

    // Disable the "Active Documents" button
    $("#active-documents-text").addClass("disabled");
});

/**
 * Shows a custom context menu.
 * @param {Event} event: The event that triggered the callback.
 */
let context_menu_document_id;
function show_context_menu(event){

    // Prevent the default context menu from appearing
    event.preventDefault();

    // Save the ID of the right-clicked document for use in other functions
    context_menu_document_id = parseInt($(this).attr("id"));

    // Set the custom context menu's position to the right-click and make it
    // visible and clickable
    let position = get_mouse_position(event);

    let page_size = get_page_size();
    let context_menu_size = get_element_size("#context-menu");
    let context_menu_far_position = point_add(position, context_menu_size);

    if(context_menu_far_position.x > page_size.x)
        position.x -= context_menu_size.x;

    if(context_menu_far_position.y > page_size.y)
        position.y -= context_menu_size.y;

    $("#context-menu").css({"pointer-events": "auto", "opacity": "1",
        "left": `${position.x}px`, "top": `${position.y}px`});
}

/**
 * Hides the custom context menu.
 * @param {Event} event: The event that triggered the callback.
 */
function hide_context_menu(event){
    $("#context-menu").css({"pointer-events": "none", "opacity": "0"});
}

/**
 * Creates a popup containing a preview of the document that was right-clicked.
 */
function preview(){

    // Send a request to get the preview of the document that was right-clicked
    send_manage_table_request("preview", context_menu_document_id)

        // If the request is successful create a popup and append the
        // HTML-escaped document preview to it
        .done(function(response){
            create_popup("Preview");
            let preview = $(`<h3 id="document-preview-text"></h3>`)
                .appendTo("#preview-popup .popup-content");
            preview.text(parse_json(response)["preview_text"]);
        })

        // If the request failed, display an error
        .fail(function(){ error(`Failed to retrieve
            the document's preview.`); });
}

/**
 * Renames the document that was right-clicked.
 */
function edit_name(){

    // Create a popup containing a text input
    create_text_input_popup("Document Name");

    // Set the popup's initial text input to the existing document name
    $("#document-name-popup .popup-input").val(
        get_manage_document(context_menu_document_id).label);

    // If the popup's "OK" button is clicked
    $("#document-name-popup .popup-ok-button").click(function(){

        // Make a request to set the name of the document that was
        // right-clicked to the content of the popup's text input field
        send_manage_table_request("edit-name", [context_menu_document_id,
            $("#document-name-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table() })

            // If the request failed, display an error
            .fail(function(){ error("Failed to edit the document's name."); });
    });
}

/**
 * Sets the class name of the document that was right-clicked.
 */
function edit_class(){

    // Create a popup containing a text input
    create_text_input_popup("Document Class");

    // Set the popup's initial text input to the existing document class
    $("#document-class-popup .popup-input").val(
        get_manage_document(context_menu_document_id).class);

    // When the popup's "OK" button is clicked
    $("#document-class-popup .popup-ok-button").click(function(){

        // Make a request to set the class of the document that was
        // right-clicked to the content of the popup's text input field
        send_manage_table_request("set-class", [context_menu_document_id,
            $("#document-class-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){ error("Failed to set the document's class."); });
    });
}

/**
 * Deletes the document that was right-clicked.
 */
function delete_document(){

    // Send a request to delete the document that was right-clicked
    send_manage_table_request("delete", context_menu_document_id)

        // If the request was successful, recreate the table
        .done(function(){ create_manage_table(); })

        // If the request failed, display an error
        .fail(function(){ error("Failed to delete the document."); });
}

/**
 * Merges the selected documents.
 */
function merge_selected(){

    let selected_document_ids = get_selected_document_ids();
    let first_selected_document =
        get_manage_document(selected_document_ids[0]);

    // Create the popup
    create_ok_popup("Merge Active");
    $(`

Name:

        <input id="merge-name-input" value="Merge-${first_selected_document.label}" type="text" spellcheck="false" autocomplete="off">

            <input id="merge-milestone-checkbox" name="" type="checkbox">

            Milestone:
            <input id="merge-milestone-input" name="" value="#EOF#" type="text" spellcheck="false" autocomplete="off">

    `).appendTo("#merge-active-popup .popup-content");

    // If the popup's "OK" button is clicked...
    $("#merge-active-popup .popup-ok-button").click(function(){

        // Create the payload
        let payload = [
            selected_document_ids,
            $("#merge-name-input").val(),
            first_selected_document.source,
            $("#merge-milestone-checkbox").is(":checked") ?
                $("#merge-milestone-input").val() : ""
        ];

        // Send the merge request
        send_manage_table_request("merge-selected", payload)

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){
                error("Failed to merge the active documents.");
            });
    });
}

/**
 * Edits the class names of the selected documents.
 */
function edit_selected_classes(){

    // Create a popup containing a text input
    create_text_input_popup("Document Class");

    // If the popup's "OK" button is clicked...
    $("#document-class-popup .popup-ok-button").click(function(){

        // Send a request to set the class names of the selected documents
        // to the value in the popup's text input
        send_manage_table_request("edit-selected-classes",
            [get_selected_document_ids(),
            $("#document-class-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){
                error("Failed to edit the active documents' classes.");
            });
    });
}

/**
 * Deletes the selected documents.
 */
function delete_selected(){

    // Send a request to delete the selected documents
    send_manage_table_request("delete-selected")

        // If the request was successful, recreate the table
        .done(function(){ create_manage_table(); })

        // If the request failed, display an error
        .fail("Failed to delete the active documents");
}

/**
 * Gets the IDs of the currently selected documents.
 * @returns {Number[]}: The selected document IDs.
 */
function get_selected_document_ids(){
    let id_list = [];
    $(".manage-table-row").each(function(){
        if($(this).hasClass("manage-table-selected-row"))
            id_list.push(parseInt($(this).attr("id")));
    });

    return id_list;
}

/**
 * Initializes the walkthrough.
 */
function walkthrough(){

    let intro = introJs();
    intro.setOptions({steps: [
        {
            intro: `Welcome to the Manage page!`,
            position: "top",
        },
        {
            element: "#active",
            intro: `Active documents will have a blue highlight to them. You
                can deactivate documents by clicking while holding down "D".`,
            position: "bottom"
        },
        {
            element: "#document",
            intro: `The Document column holds custom document names. You can
                rename documents by right-clicking.`,
            position: "bottom"
        },
        {
            element: "#class",
            intro: `If you want to group documents together, you may give them
                a class by right-clicking.`,
            position: "bottom"
        },
        {
            element: "#excerpt",
            intro: `Excerpt will give you a preview of your document.`,
            position: "bottom"
        },
        {
            element: "#active-documents-footer",
            intro: `By clicking Active Documents in any page, you can quickly
            access a popup where you can toggle documents in-page.`,
            position: "bottom"
        },
        {
            element: "#help-button",
            intro: `Check our Help section for more advice on the Manage
                page.`,
            position: "bottom"
        },
        {
            element: "#prepare-button",
            intro: `Once you"re satisfied with your active documents, you can
                move on to the "Prepare" pages.`,
            position: "bottom"
        },
        {
            intro: `This concludes the Manage walkthrough!`,
            position: "top",
        }
    ]});

    return intro;
}
<commit_msg>removed a bit<commit_after>$(function(){

    // Create the manage table
    initialize_manage_table("#manage-table", true);

    // Create the context menu hide callbacks
    $(window).on("mousedown resize", hide_context_menu);
    $("#main-section").on("scroll", hide_context_menu);

    // Create the context menu button callbacks
    $("#preview-button").mousedown(preview);
    $("#edit-name-button").mousedown(edit_name);
    $("#edit-class-button").mousedown(edit_class);
    $("#delete-button").mousedown(delete_document);
    $("#merge-selected-button").mousedown(merge_selected);
    $("#edit-selected-classes-button").mousedown(edit_selected_classes);
    $("#delete-selected-button").mousedown(delete_selected);
    $("#select-all-button").mousedown(manage_table_select_all);
    $("#deselect-all-button").mousedown(manage_table_deselect_all);

    // Initialize the walkthrough
    initialize_walkthrough(walkthrough);

    // Disable the "Active Documents" button
    $("#active-documents-text").addClass("disabled");
});

/**
 * Shows a custom context menu.
 * @param {Event} event: The event that triggered the callback.
 */
let context_menu_document_id;
function show_context_menu(event){

    // Prevent the default context menu from appearing
    event.preventDefault();

    // Save the ID of the right-clicked document for use in other functions
    context_menu_document_id = parseInt($(this).attr("id"));

    // Set the custom context menu's position to the right-click and make it
    // visible and clickable
    let position = get_mouse_position(event);

    let page_size = get_page_size();
    let context_menu_size = get_element_size("#context-menu");
    let context_menu_far_position = point_add(position, context_menu_size);

    if(context_menu_far_position.x > page_size.x)
        position.x -= context_menu_size.x;

    if(context_menu_far_position.y > page_size.y)
        position.y -= context_menu_size.y;

    $("#context-menu").css({"pointer-events": "auto", "opacity": "1",
        "left": `${position.x}px`, "top": `${position.y}px`});
}

/**
 * Hides the custom context menu.
 * @param {Event} event: The event that triggered the callback.
 */
function hide_context_menu(event){
    $("#context-menu").css({"pointer-events": "none", "opacity": "0"});
}

/**
 * Creates a popup containing a preview of the document that was right-clicked.
 */
function preview(){

    // Send a request to get the preview of the document that was right-clicked
    send_manage_table_request("preview", context_menu_document_id)

        // If the request is successful create a popup and append the
        // HTML-escaped document preview to it
        .done(function(response){
            create_popup("Preview");
            let preview = $(`<h3 id="document-preview-text"></h3>`)
                .appendTo("#preview-popup .popup-content");
            preview.text(parse_json(response)["preview_text"]);
        })

        // If the request failed, display an error
        .fail(function(){ error(`Failed to retrieve
            the document's preview.`); });
}

/**
 * Renames the document that was right-clicked.
 */
function edit_name(){

    // Create a popup containing a text input
    create_text_input_popup("Document Name");

    // Set the popup's initial text input to the existing document name
    $("#document-name-popup .popup-input").val(
        get_manage_document(context_menu_document_id).label);

    // If the popup's "OK" button is clicked
    $("#document-name-popup .popup-ok-button").click(function(){

        // Make a request to set the name of the document that was
        // right-clicked to the content of the popup's text input field
        send_manage_table_request("edit-name", [context_menu_document_id,
            $("#document-name-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table() })

            // If the request failed, display an error
            .fail(function(){ error("Failed to edit the document's name."); });
    });
}

/**
 * Sets the class name of the document that was right-clicked.
 */
function edit_class(){

    // Create a popup containing a text input
    create_text_input_popup("Document Class");

    // Set the popup's initial text input to the existing document class
    $("#document-class-popup .popup-input").val(
        get_manage_document(context_menu_document_id).class);

    // When the popup's "OK" button is clicked
    $("#document-class-popup .popup-ok-button").click(function(){

        // Make a request to set the class of the document that was
        // right-clicked to the content of the popup's text input field
        send_manage_table_request("set-class", [context_menu_document_id,
            $("#document-class-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){ error("Failed to set the document's class."); });
    });
}

/**
 * Deletes the document that was right-clicked.
 */
function delete_document(){

    // Send a request to delete the document that was right-clicked
    send_manage_table_request("delete", context_menu_document_id)

        // If the request was successful, recreate the table
        .done(function(){ create_manage_table(); })

        // If the request failed, display an error
        .fail(function(){ error("Failed to delete the document."); });
}

/**
 * Merges the selected documents.
 */
function merge_selected(){

    let selected_document_ids = get_selected_document_ids();
    let first_selected_document =
        get_manage_document(selected_document_ids[0]);

    // Create the popup
    create_ok_popup("Merge Active");
    $(`

Name:

        <input id="merge-name-input" value="Merge-${first_selected_document.label}" type="text" spellcheck="false" autocomplete="off">

            <input id="merge-milestone-checkbox" name="" type="checkbox">

            Milestone:
            <input id="merge-milestone-input" name="" value="#EOF#" type="text" spellcheck="false" autocomplete="off">

    `).appendTo("#merge-active-popup .popup-content");

    // If the popup's "OK" button is clicked...
    $("#merge-active-popup .popup-ok-button").click(function(){

        // Create the payload
        let payload = [
            selected_document_ids,
            $("#merge-name-input").val(),
            first_selected_document.source,
            $("#merge-milestone-checkbox").is(":checked") ?
                $("#merge-milestone-input").val() : ""
        ];

        // Send the merge request
        send_manage_table_request("merge-selected", payload)

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){
                error("Failed to merge the active documents.");
            });
    });
}

/**
 * Edits the class names of the selected documents.
 */
function edit_selected_classes(){

    // Create a popup containing a text input
    create_text_input_popup("Document Class");

    // If the popup's "OK" button is clicked...
    $("#document-class-popup .popup-ok-button").click(function(){

        // Send a request to set the class names of the selected documents
        // to the value in the popup's text input
        send_manage_table_request("edit-selected-classes",
            [get_selected_document_ids(),
            $("#document-class-popup .popup-input").val()])

            // If the request was successful, close the popup and recreate the
            // table
            .done(function(){ close_popup(); create_manage_table(); })

            // If the request failed, display an error
            .fail(function(){
                error("Failed to edit the active documents' classes.");
            });
    });
}

/**
 * Deletes the selected documents.
 */
function delete_selected(){

    // Send a request to delete the selected documents
    send_manage_table_request("delete-selected")

        // If the request was successful, recreate the table
        .done(function(){ create_manage_table(); })

        // If the request failed, display an error
        .fail("Failed to delete the active documents");
}

/**
 * Gets the IDs of the currently selected documents.
 * @returns {Number[]}: The selected document IDs.
 */
function get_selected_document_ids(){
    let id_list = [];
    $(".manage-table-row").each(function(){
        if($(this).hasClass("manage-table-selected-row"))
            id_list.push(parseInt($(this).attr("id")));
    });

    return id_list;
}

/**
 * Initializes the walkthrough.
 */
function walkthrough(){

    let intro = introJs();
    intro.setOptions({steps: [
        {
            intro: `Welcome to the Manage page!`,
            position: "top",
        },
        {
            element: "#active",
            intro: `Active documents will have a blue highlight to them. You
                can deactivate documents by clicking while holding down "D".`,
            position: "bottom"
        },
        {
            element: "#document",
            intro: `The Document column holds custom document names. You can
                rename documents by right-clicking.`,
            position: "bottom"
        },
        {
            element: "#class",
            intro: `If you want to group documents together, you may give them
                a class by right-clicking.`,
            position: "bottom"
        },
        {
            element: "#excerpt",
            intro: `Excerpt will give you a preview of your document.`,
            position: "bottom"
        },
        {
            element: "#help-button",
            intro: `Check our Help section for more advice on the Manage
                page.`,
            position: "bottom"
        },
        {
            element: "#prepare-button",
            intro: `Once you"re satisfied with your active documents, you can
                move on to the "Prepare" pages.`,
            position: "bottom"
        },
        {
            intro: `This concludes the Manage walkthrough!`,
            position: "top",
        }
    ]});

    return intro;
}
<|endoftext|>


<commit_before>'use strict';

module.exports = {
  range
}

function range (date) {
  if (date.date) {
    return { from: date.date, to: date.date }
  }

  if (date[ 'date-period' ]) {
    return {
      from: date[ 'date-period' ].split('/')[ 0 ],
      to: date[ 'date-period' ].split('/')[ 1 ]
    }
  }

  if(isNaN(Date.parse(date)) === false){
    return range({date})
  }

  throw "Unable to parse the date"
}
<commit_msg>Define custom error<commit_after>'use strict';

module.exports = {
  range
}

function range (date) {
  if (date.date) {
    return { from: date.date, to: date.date }
  }

  if (date[ 'date-period' ]) {
    return {
      from: date[ 'date-period' ].split('/')[ 0 ],
      to: date[ 'date-period' ].split('/')[ 1 ]
    }
  }

  if(isNaN(Date.parse(date)) === false){
    return range({date})
  }

  throw new InvalidDateEntity();
}

function InvalidDateEntity(message) {
  this.name = 'InvalidDateEntity';
  this.message = message || 'Unable to parse the date';
  this.stack = (new Error()).stack;
}
InvalidDateEntity.prototype = Object.create(Error.prototype);
InvalidDateEntity.prototype.constructor = InvalidDateEntity;
<|endoftext|>