<commit_before>define([], function(){
    return function(){

        /* if (App.settings.server) $("#serverIP").val(App.settings.server);
         $("#serverIP").on('input', function(e){setTimeout(function(){App.settings.server = $("#serverIP").val();},0)});
         if (App.settings.username) $("#username").val(App.settings.username);
         $("#username").on('input', function(e){setTimeout(function(){App.settings.username = $("#username").val();},0)});
         $("#password").on('input', function(e){setTimeout(function(){App.settings.password = $("#password").val();},0)});
         if (App.settings.password) $("#password").val(App.settings.password);
         if (App.settings.cubeName) $("#cube").val(App.settings.cubeName);
         $("#cube").on('input', function(e){setTimeout(function(){App.settings.cubeName = $("#cube").val();},0)});
         if (App.settings.namespace) $("#namespace").val(App.settings.namespace);
         $("#namespace").on('input', function(e){setTimeout(function(){App.settings.namespace = $("#namespace").val();},0)});
         */
         var servers;
         if (localStorage.servers) servers = JSON.parse(localStorage.servers);

         if (servers) {
            var $lst = $("#lstServers");
            for (var i = 0; i < servers.length; i++) {
                var idx = (i+1).toString();
                var name = servers[i].name;
                if (!name) name = servers[i].ip;
                var $item = $('
' + name + '<button id="btnEditServer' + idx + '" class="btn">Edit</button><button id="btnDeleteServer' + idx + '" class="btn btn-negative" style="display:none">Delete</button>
');
                $item.attr("serverIndex", i.toString()).appendTo($lst);
                if (localStorage.currentServerId == i) {
                    $item.addClass("active");
                }
                $item.off('tap').on('tap', SetCurrentServer);
                $("#btnEditServer" + (i+1).toString()).off('tap').on('tap', EditServer);
                $("#btnDeleteServer" + (i+1).toString()).off('tap').on('tap', DeleteServer);
            }
        }

        function SetCurrentServer(e) {
            var id = $(e.currentTarget).attr("serverIndex");
            $("#lstServers .active").removeClass("active");
            $(e.currentTarget).addClass("active");
            localStorage.currentServerId = id;
            App.m.publish("viewchange:Login", { holder: "#loginScreen" });
        }

        function EditServer(e) {
            var id = $(e.currentTarget).parent().attr("serverIndex");
            App.m.publish("viewchange:ServerInfo", { holder: "#loginScreen", id: id });
        }

        function DeleteServer(e) {
            var id = $(e.currentTarget).parent().attr("serverIndex");
            if (id != undefined) {
                var servers;
                if (localStorage.servers) servers = JSON.parse(localStorage.servers);
                if (servers) {
                    if (localStorage.currentServerId == id) delete localStorage.currentServerId;
                    if (servers[id]) servers.splice(id, 1);
                    $(".table-view-cell:eq(" + id + ")").remove();
                    localStorage.servers = JSON.stringify(servers);
                }
            }
        }

        $("#btnDelete").off('tap').on('tap', function() {
            if ($(".table-view-cell").length == 0) return;
            $("#btnDone").show();
            $("#btnAdd").hide();
            $(".table-view-cell .btn").hide();
            $(".table-view-cell .btn-negative").show();
        });
        $("#btnDone").off('tap').on('tap', function() {
            $("#btnDone").hide();
            $("#btnAdd").show();
            $(".table-view-cell .btn").show();
            $(".table-view-cell .btn-negative").hide();
        });

        $("#btnBack").off('tap').on('tap', function() {
            App.m.publish("viewchange:Login", { holder: "#loginScreen" });
        });
        $("#btnAdd").off('tap').on('tap', function() {
            App.m.publish("viewchange:ServerInfo", { holder: "#loginScreen" });
        });

    };
});<commit_msg>redesign<commit_after>define([], function(){
    return function(){

        /* if (App.settings.server) $("#serverIP").val(App.settings.server);
         $("#serverIP").on('input', function(e){setTimeout(function(){App.settings.server = $("#serverIP").val();},0)});
         if (App.settings.username) $("#username").val(App.settings.username);
         $("#username").on('input', function(e){setTimeout(function(){App.settings.username = $("#username").val();},0)});
         $("#password").on('input', function(e){setTimeout(function(){App.settings.password = $("#password").val();},0)});
         if (App.settings.password) $("#password").val(App.settings.password);
         if (App.settings.cubeName) $("#cube").val(App.settings.cubeName);
         $("#cube").on('input', function(e){setTimeout(function(){App.settings.cubeName = $("#cube").val();},0)});
         if (App.settings.namespace) $("#namespace").val(App.settings.namespace);
         $("#namespace").on('input', function(e){setTimeout(function(){App.settings.namespace = $("#namespace").val();},0)});
         */
         var servers;
         if (localStorage.servers) servers = JSON.parse(localStorage.servers);

         if (servers) {
            var $lst = $("#lstServers");
            for (var i = 0; i < servers.length; i++) {
                var idx = (i+1).toString();
                var name = servers[i].name;
                if (!name) name = servers[i].ip;
                var $item = $('
' + name + '<button id="btnEditServer' + idx + '" class="btn">Edit</button><button id="btnDeleteServer' + idx + '" class="btn btn-negative" style="display:none">Delete</button>
');
                $item.attr("serverIndex", i.toString()).appendTo($lst);
                if (localStorage.currentServerId == i) {
                    $item.addClass("active");
                }
                $item.off('tap').on('tap', SetCurrentServer);
                $("#btnEditServer" + (i+1).toString()).off('tap').on('tap', EditServer);
                $("#btnDeleteServer" + (i+1).toString()).off('tap').on('tap', DeleteServer);
            }
        }

        function SetCurrentServer(e) {
            var id = $(e.currentTarget).attr("serverIndex");
            $("#lstServers .active").removeClass("active");
            $(e.currentTarget).addClass("active");
            localStorage.currentServerId = id;
            App.m.publish("viewchange:Login", { holder: "#loginScreen" });
        }

        function EditServer(e) {
            var id = $(e.currentTarget).parent().attr("serverIndex");
            App.m.publish("viewchange:ServerInfo", { holder: "#loginScreen", id: id });
        }

        function DeleteServer(e) {
            var id = $(e.currentTarget).parent().attr("serverIndex");
            if (id != undefined) {
                var servers;
                if (localStorage.servers) servers = JSON.parse(localStorage.servers);
                if (servers) {
                    if (localStorage.currentServerId == id) delete localStorage.currentServerId;
                    if (servers[id]) servers.splice(id, 1);
                    $(".table-view-cell:eq(" + id + ")").remove();
                    localStorage.servers = JSON.stringify(servers);
                }
            }
        }

        $("#btnDelete").off('tap').on('tap', function() {
            if ($(".table-view-cell").length == 0) return;
            $("#btnDone").show();
            $("#btnAdd").hide();
            $(".table-view-cell .btn").hide();
            $(".table-view-cell .btn-negative").show();
        });
        $("#btnDone").off('tap').on('tap', function() {
            $("#btnDone").hide();
            $("#btnAdd").show();
            $(".table-view-cell .btn").show();
            $(".table-view-cell .btn-negative").hide();
        });

        $("#btnBack").off('tap').on('tap', function() {
            App.m.publish("viewchange:Login", { holder: "#loginScreen" });
        });
        $("#btnAdd").off('tap').on('tap', function() {
            App.m.publish("viewchange:ServerInfo", { holder: "#loginScreen" });
        });

    };
});<|endoftext|>


<commit_before>var line = [];
var backstory = null;

var wordIndex = 0;
var lineIndex = 0;

var wordDelay = 120;
var lineDelay = 400;
var content = [
    "Ivan, ",
    "You are terrible.",
    "You do not judge like your comrades.",
    "Americanski says you either take bribe",
    "  or never learn how to judge.",
    "You disgrace mother Russia!",
    "Interpretive dance serious business!",
    "We send you back to beginning, ",
    "  learn judging all over again.",
    "Then MAYBE you return for next Olympics.",
    "",
    "-- Minister of Cultural Emergencies"
];

var startMenu = function(game) {
    return {
        preload: function() {
            game.stage.backgroundColor = "#aaaaaa";
            this.game.scale.pageAlignHorizontally = true;
            this.game.scale.pageAlignVertically = true;
            this.game.scale.refresh();

            this.centeredText("Russian Judge Redemption", 20);
            this.loadingText = this.centeredText("Loading", game.world.centerY);
            game.load.image('floor', 'assets/floorTile/draftFloor.png');
            game.load.image('judgeResponseGood', 'assets/judgeResponseGood.jpg');
            game.load.image('judgeResponseBad', 'assets/judgeResponseBad.jpg');
            game.load.image('feedbackResultGood', 'assets/vaultBoyGood.jpg');
            game.load.image('feedbackResultBad', 'assets/vaultBoyBad.jpg');
            game.load.image('meterArrow', 'assets/meter-arrow.png');
            game.load.image('voteScoreBar', 'assets/vote-score-bar.png');
            game.load.image('russianJudge', 'assets/judge/russianJudge/normalSprite.png');
            game.load.image('draftJudge', 'assets/judge/draftJudge/normalSprite.png');
            game.load.image('judge1', 'assets/judge/1Judge/normalSprite.png');
            game.load.image('judge2', 'assets/judge/2Judge/normalSprite.png');
            game.load.image('judge3', 'assets/judge/3Judge/normalSprite.png');
            game.load.image('audience', 'assets/audience/audience.png');

            game.load.image('dancer-head', 'assets/dancer/mikeDancer/head.png');
            game.load.image('dancer-leftArm', 'assets/dancer/mikeDancer/leftArm.png');
            game.load.image('dancer-rightArm', 'assets/dancer/mikeDancer/rightArm.png');
            game.load.image('dancer-leftHand', 'assets/dancer/mikeDancer/leftHand.png');
            game.load.image('dancer-rightHand', 'assets/dancer/mikeDancer/rightHand.png');
            game.load.image('dancer-leftFoot', 'assets/dancer/mikeDancer/leftFoot.png');
            game.load.image('dancer-rightFoot', 'assets/dancer/mikeDancer/rightFoot.png');
            game.load.image('dancer-leftLeg', 'assets/dancer/mikeDancer/leftLeg.png');
            game.load.image('dancer-rightLeg', 'assets/dancer/mikeDancer/rightLeg.png');
            game.load.image('dancer-torso', 'assets/dancer/mikeDancer/torso.png');

            game.load.audio('polka', 'assets/audio/LeekSpin.mp3');
            game.load.audio('dark', 'assets/audio/DarkOminousMusicShort.mp3')
            game.load.audio('boo', 'assets/audio/BooShort.mp3');
            game.load.audio('cheer', 'assets/audio/CheerShort.mp3');

            game.load.onLoadComplete.add(function() {
                // TODO kill this so you have an actual start menu
                //game.state.start('dancing');

                this.loadingText.destroy();
                var startButton = this.centeredText("Start Judging", game.world.centerY);
                startButton.inputEnabled = true;
                startButton.events.onInputDown.add(
                    function() {
                      game.stage.backgroundColor = "black";
                      var style = {
                          font: "32px Arial",
                          fill: "#19de65",
                          wordWrap: false,
                          //align: "center",
                          backgroundColor: "black"
                      };

                        backstory = game.add.text(32, 32, '', style);
                        startButton.destroy();
                        displayBackstory();

                    });
            }, this);
        },
        centeredText: function(ctext, y) {
            ctext = game.add.text(game.world.centerX, y, ctext);
            ctext.anchor.setTo(0.5, 0.5);
            return ctext;
        },
    };
};

function displayBackstory() {
  music = game.add.audio('dark');
  music.play();

  nextLine();

}

function nextLine() {

    if (lineIndex === content.length)
    {
        //  We're finished
        game.time.events.loop(Phaser.Timer.SECOND * 3, function() {
        music.stop();
        game.state.start('dancing');
        }, this);
        return;
    }

    //  Split the current line on spaces, so one word per array element
    line = content[lineIndex].split(' ');

    //  Reset the word index to zero (the first word in the line)
    wordIndex = 0;

    //  Call the 'nextWord' function once for each word in the line (line.length)
    game.time.events.repeat(wordDelay, line.length, nextWord, this);

    //  Advance to the next line
    lineIndex++;

}

function nextWord() {

    //  Add the next word onto the text string, followed by a space
    backstory.text = backstory.text.concat(line[wordIndex] + " ");

    //  Advance the word index to the next word in the line
    wordIndex++;

    //  Last word?
    if (wordIndex === line.length)
    {
        //  Add a carriage return
        backstory.text = backstory.text.concat("\n");

        //  Get the next line after the lineDelay amount of ms has elapsed
        game.time.events.add(lineDelay, nextLine, this);
    }

}

game.state.add('startMenu', startMenu);
<commit_msg>upped volume on intro music<commit_after>var line = [];
var backstory = null;

var wordIndex = 0;
var lineIndex = 0;

var wordDelay = 120;
var lineDelay = 400;
var content = [
    "Ivan, ",
    "You are terrible.",
    "You do not judge like your comrades.",
    "Americanski says you either take bribe",
    "  or never learn how to judge.",
    "You disgrace mother Russia!",
    "Interpretive dance serious business!",
    "We send you back to beginning, ",
    "  learn judging all over again.",
    "Then MAYBE you return for next Olympics.",
    "",
    "-- Minister of Cultural Emergencies"
];

var startMenu = function(game) {
    return {
        preload: function() {
            game.stage.backgroundColor = "#aaaaaa";
            this.game.scale.pageAlignHorizontally = true;
            this.game.scale.pageAlignVertically = true;
            this.game.scale.refresh();

            this.centeredText("Russian Judge Redemption", 20);
            this.loadingText = this.centeredText("Loading", game.world.centerY);
            game.load.image('floor', 'assets/floorTile/draftFloor.png');
            game.load.image('judgeResponseGood', 'assets/judgeResponseGood.jpg');
            game.load.image('judgeResponseBad', 'assets/judgeResponseBad.jpg');
            game.load.image('feedbackResultGood', 'assets/vaultBoyGood.jpg');
            game.load.image('feedbackResultBad', 'assets/vaultBoyBad.jpg');
            game.load.image('meterArrow', 'assets/meter-arrow.png');
            game.load.image('voteScoreBar', 'assets/vote-score-bar.png');
            game.load.image('russianJudge', 'assets/judge/russianJudge/normalSprite.png');
            game.load.image('draftJudge', 'assets/judge/draftJudge/normalSprite.png');
            game.load.image('judge1', 'assets/judge/1Judge/normalSprite.png');
            game.load.image('judge2', 'assets/judge/2Judge/normalSprite.png');
            game.load.image('judge3', 'assets/judge/3Judge/normalSprite.png');
            game.load.image('audience', 'assets/audience/audience.png');

            game.load.image('dancer-head', 'assets/dancer/mikeDancer/head.png');
            game.load.image('dancer-leftArm', 'assets/dancer/mikeDancer/leftArm.png');
            game.load.image('dancer-rightArm', 'assets/dancer/mikeDancer/rightArm.png');
            game.load.image('dancer-leftHand', 'assets/dancer/mikeDancer/leftHand.png');
            game.load.image('dancer-rightHand', 'assets/dancer/mikeDancer/rightHand.png');
            game.load.image('dancer-leftFoot', 'assets/dancer/mikeDancer/leftFoot.png');
            game.load.image('dancer-rightFoot', 'assets/dancer/mikeDancer/rightFoot.png');
            game.load.image('dancer-leftLeg', 'assets/dancer/mikeDancer/leftLeg.png');
            game.load.image('dancer-rightLeg', 'assets/dancer/mikeDancer/rightLeg.png');
            game.load.image('dancer-torso', 'assets/dancer/mikeDancer/torso.png');

            game.load.audio('polka', 'assets/audio/LeekSpin.mp3');
            game.load.audio('dark', 'assets/audio/DarkOminousMusicShort.mp3')
            game.load.audio('boo', 'assets/audio/BooShort.mp3');
            game.load.audio('cheer', 'assets/audio/CheerShort.mp3');

            game.load.onLoadComplete.add(function() {
                // TODO kill this so you have an actual start menu
                //game.state.start('dancing');

                this.loadingText.destroy();
                var startButton = this.centeredText("Start Judging", game.world.centerY);
                startButton.inputEnabled = true;
                startButton.events.onInputDown.add(
                    function() {
                      game.stage.backgroundColor = "black";
                      var style = {
                          font: "32px Arial",
                          fill: "#19de65",
                          wordWrap: false,
                          //align: "center",
                          backgroundColor: "black"
                      };

                        backstory = game.add.text(32, 32, '', style);
                        startButton.destroy();
                        displayBackstory();

                    });
            }, this);
        },
        centeredText: function(ctext, y) {
            ctext = game.add.text(game.world.centerX, y, ctext);
            ctext.anchor.setTo(0.5, 0.5);
            return ctext;
        },
    };
};

function displayBackstory() {
  music = game.add.audio('dark');
  music.play();
  music.volume += 1.0;

  nextLine();

}

function nextLine() {

    if (lineIndex === content.length)
    {
        //  We're finished
        game.time.events.loop(Phaser.Timer.SECOND * 3, function() {
        music.stop();
        game.state.start('dancing');
        }, this);
        return;
    }

    //  Split the current line on spaces, so one word per array element
    line = content[lineIndex].split(' ');

    //  Reset the word index to zero (the first word in the line)
    wordIndex = 0;

    //  Call the 'nextWord' function once for each word in the line (line.length)
    game.time.events.repeat(wordDelay, line.length, nextWord, this);

    //  Advance to the next line
    lineIndex++;

}

function nextWord() {

    //  Add the next word onto the text string, followed by a space
    backstory.text = backstory.text.concat(line[wordIndex] + " ");

    //  Advance the word index to the next word in the line
    wordIndex++;

    //  Last word?
    if (wordIndex === line.length)
    {
        //  Add a carriage return
        backstory.text = backstory.text.concat("\n");

        //  Get the next line after the lineDelay amount of ms has elapsed
        game.time.events.add(lineDelay, nextLine, this);
    }

}

game.state.add('startMenu', startMenu);
<|endoftext|>


<commit_before>var convertReg = /^(?:object|function)$/;
var check = function (o) {
    return o === null || typeof o === 'bigint' || o instanceof BigInt || typeof o === 'number' || typeof o === "boolean";
};
var string = function (a) {
    return "\"" + String(a).replace(/\\/g, "\\\\").replace(/"/g, '\\$&') + "\"";
};
var symbol = function (a) {
    return "\'" + String(a).replace(/^\w*\(([\s\S]*)\)$/, '$1').replace(/\\/g, "\\\\").replace(/'/g, '\\$&') + "\'";
};
var date = function (d) {
    return d.toISOString();
};
var regrep = a => a === '/' ? "\\/" : a;
var regexp = function (r) {
    return '/' + r.source.replace(/\\[\s\S]|\//g, regrep) + '/' + r.flags;
};
var join = function (o) {
    if (o === undefined) return '';
    if (check(o)) return String(o);
    if (typeof o === 'symbol' || o instanceof Symbol) return symbol(o);
    if (typeof o !== 'object') return string(o);
    if (o instanceof Date) return date(o);
    if (o instanceof RegExp) return regexp(o);
    var arr = o[""];
    delete o[""];
    var pairs = [].concat(arr);
    for (var k in o) {
        pairs.push(k + ':' + o[k]);
    }
    if (o instanceof Array) {
        return `[${pairs.join(',')}]`;
    }
    return `{${pairs.join(',')}}`;
}
function stringify(memery) {
    if (memery === undefined) return '';
    if (check(memery)) return String(memery);
    if (typeof memery === 'symbol') return symbol(memery);
    if (memery instanceof Date) return date(memery);
    if (memery instanceof RegExp) return regexp(memery);
    if (!convertReg.test(typeof memery)) return string(memery);
    var dist = [memery];
    var rest = [memery];
    var trimed = [memery instanceof Array ? [] : {}];
    var objects = [trimed[0]];
    while (rest.length) {
        var memery = rest.shift();
        var o = objects.shift();
        var inc = 0, arr = [];
        o[""] = arr;
        for (var k in memery) {
            var m = memery[k];
            if (inc === +k) {
                var kindex = "";
                inc++;
            } else {
                var kindex = dist.indexOf(k);
                if (!~kindex) {
                    kindex = dist.length;
                    dist.push(k);
                    trimed.push(k);
                }
            }
            var index = dist.indexOf(m);
            if (!~index) {
                index = dist.length;
                dist.push(m);
                if (m !== null) {
                    if (m instanceof Date) {
                        trimed.push(m);
                    }
                    else if (m instanceof RegExp) {
                        trimed.push(m);
                    }
                    else if (convertReg.test(typeof m)) {
                        rest.push(m);
                        var t = m instanceof Array ? [] : {};
                        trimed.push(t);
                        objects.push(t);
                    }
                    else {
                        trimed.push(m);
                    }
                } else {
                    trimed.push(m);
                }
            }
            if (kindex === '') {
                arr.push(index);
            } else {
                o[kindex] = index;
            }
        }
    }
    return trimed.map(join).join(',');
}
var create = function (a, dst) {
    if (!a) return;
    var arr = a.split(',');
    var rest = [];
    for (var cx = 0, dx = arr.length; cx < dx; cx++) {
        var [k, v] = arr[cx].split(':');
        if (v === undefined) {
            rest.push(k);
        } else {
            dst[k] = +v;
        }
    }
    dst[""] = rest;
};
function parse(string) {
    string = String(string);
    var trimed = [];
    var reg1 = /\}/g;
    var reg2 = /\]/g;
    var reg3 = /\\[\s\S]|"/g;
    var reg4 = /,|$/g;
    var reg5 = /\\[\s\S]|\//g;
    var reg6 = /\\[\s\S]|'/g;
    var marked = [];
    for (var cx = 0, dx = string.length; cx < dx; cx++) {
        var s = string.charAt(cx);
        var reg = null, o = null;
        switch (s) {
            case ",":
                trimed.push(undefined);
                break;
            case "{":
                reg = reg1;
                o = {};
            case "[":
                o = o || [];
                reg = reg || reg2;
                reg.lastIndex = cx + 1;
                reg.exec(string);
                var index = reg.lastIndex;
                var s = string.slice(cx + 1, index - 1);
                create(s, o);
                marked.push(trimed.length);
                trimed.push(o);
                cx = reg.lastIndex;
                break;
            case "/":
                reg = reg5;
                o = 1;
            case "'":
                reg = reg || reg6;
                o = o || 2;
            case "\"":
                reg = reg || reg3;
                reg.lastIndex = cx + 1;
                do {
                    var s = reg.exec(string);
                } while (s && s[0].length === 2);
                var index = reg.lastIndex;
                s = string.slice(cx + 1, index - 1).replace(/\\([\s\S])/g, '$1');
                cx = index;
                if (o == 2) {
                    s = Symbol(s);
                } else if (o == 1) {
                    reg4.lastIndex = index;
                    var m = reg4.exec(string);
                    index = m.index;
                    var flag = string.slice(cx, index);
                    cx = index;
                    s = new RegExp(s, flag);
                }
                trimed.push(s);
                break;
            case "/":
                break;
            default:
                reg4.lastIndex = cx + 1;
                var match = reg4.exec(string);
                var index = match.index;

                var s = string.slice(cx, index);
                cx = index;
                switch (s) {
                    case "null":
                        s = null;
                        break;
                    case "true":
                        s = true;
                        break;
                    case "false":
                        s = false;
                        break;
                    default:
                        if (/^\d+[\/\-]/i.test(s)) {
                            s = new Date(s);
                        }
                        else if (/^\-?\d+$/.test(s)) {
                            if (s.length > 15) {
                                s = BigInt(s);
                            } else {
                                s = parseInt(s);
                            }
                        } else {
                            s = parseFloat(s);
                        }

                }
                trimed.push(s);
        }
    }
    var dist = trimed.slice(0, trimed.length);
    for (var cx = 0, dx = marked.length; cx < dx; cx++) {
        var index = marked[cx];
        var o = trimed[index];
        dist[index] = o instanceof Array ? [] : {};
    }
    for (var cx = 0, dx = marked.length; cx < dx; cx++) {
        var index = marked[cx];
        var o = trimed[index];
        var t = dist[index];
        var arr = o[""];
        delete o[""];
        for (var k in arr) {
            t[k] = dist[arr[k]];
        }
        for (var k in o) {
            var v = o[k];
            t[dist[k]] = dist[v];
        }
    }
    return dist[0];
}
module.exports = {
    stringify,
    parse(data) {
        if (!/^\s*([\[\{]|\[\s*\]|\{\s*\})/.test(data)) return parse(data);
        if (/^\s*\{[\d\,\:\s]*\}\s*,/.test(data)) return parse(data);
        if (/^\s*\[[\d\,\:\s]*\]\s*,/.test(data)) return parse(data);
        return JSON.parse(data);
    }
};<commit_msg>修复不支持空字符串属性的问题<commit_after>var convertReg = /^(?:object|function)$/;
var check = function (o) {
    return o === null || typeof o === 'bigint' || o instanceof BigInt || typeof o === 'number' || typeof o === "boolean";
};
var string = function (a) {
    return "\"" + String(a).replace(/\\/g, "\\\\").replace(/"/g, '\\$&') + "\"";
};
var symbol = function (a) {
    return "\'" + String(a).replace(/^\w*\(([\s\S]*)\)$/, '$1').replace(/\\/g, "\\\\").replace(/'/g, '\\$&') + "\'";
};
var date = function (d) {
    return d.toISOString();
};
var regrep = a => a === '/' ? "\\/" : a;
var regexp = function (r) {
    return '/' + r.source.replace(/\\[\s\S]|\//g, regrep) + '/' + r.flags;
};
var join = function (o) {
    if (o === undefined) return '';
    if (check(o)) return String(o);
    if (typeof o === 'symbol' || o instanceof Symbol) return symbol(o);
    if (typeof o !== 'object') return string(o);
    if (o instanceof Date) return date(o);
    if (o instanceof RegExp) return regexp(o);
    var arr = o[""];
    delete o[""];
    var pairs = [].concat(arr);
    for (var k in o) {
        pairs.push(k + ':' + o[k]);
    }
    if (o instanceof Array) {
        return `[${pairs.join(',')}]`;
    }
    return `{${pairs.join(',')}}`;
}
function stringify(memery) {
    if (memery === undefined) return '';
    if (check(memery)) return String(memery);
    if (typeof memery === 'symbol') return symbol(memery);
    if (memery instanceof Date) return date(memery);
    if (memery instanceof RegExp) return regexp(memery);
    if (!convertReg.test(typeof memery)) return string(memery);
    var dist = [memery];
    var rest = [memery];
    var trimed = [memery instanceof Array ? [] : {}];
    var objects = [trimed[0]];
    while (rest.length) {
        var memery = rest.shift();
        var o = objects.shift();
        var inc = 0, arr = [];
        o[""] = arr;
        for (var k in memery) {
            var m = memery[k];
            if (inc === +k && k !== '') {
                var kindex = "";
                inc++;
            } else {
                var kindex = dist.indexOf(k);
                if (!~kindex) {
                    kindex = dist.length;
                    dist.push(k);
                    trimed.push(k);
                }
            }
            var index = dist.indexOf(m);
            if (!~index) {
                index = dist.length;
                dist.push(m);
                if (m !== null) {
                    if (m instanceof Date) {
                        trimed.push(m);
                    }
                    else if (m instanceof RegExp) {
                        trimed.push(m);
                    }
                    else if (convertReg.test(typeof m)) {
                        rest.push(m);
                        var t = m instanceof Array ? [] : {};
                        trimed.push(t);
                        objects.push(t);
                    }
                    else {
                        trimed.push(m);
                    }
                } else {
                    trimed.push(m);
                }
            }
            if (kindex === '') {
                arr.push(index);
            } else {
                o[kindex] = index;
            }
        }
    }
    return trimed.map(join).join(',');
}
var create = function (a, dst) {
    if (!a) return;
    var arr = a.split(',');
    var rest = [];
    for (var cx = 0, dx = arr.length; cx < dx; cx++) {
        var [k, v] = arr[cx].split(':');
        if (v === undefined) {
            rest.push(k);
        } else {
            dst[k] = +v;
        }
    }
    dst[""] = rest;
};
function parse(string) {
    string = String(string);
    var trimed = [];
    var reg1 = /\}/g;
    var reg2 = /\]/g;
    var reg3 = /\\[\s\S]|"/g;
    var reg4 = /,|$/g;
    var reg5 = /\\[\s\S]|\//g;
    var reg6 = /\\[\s\S]|'/g;
    var marked = [];
    for (var cx = 0, dx = string.length; cx < dx; cx++) {
        var s = string.charAt(cx);
        var reg = null, o = null;
        switch (s) {
            case ",":
                trimed.push(undefined);
                break;
            case "{":
                reg = reg1;
                o = {};
            case "[":
                o = o || [];
                reg = reg || reg2;
                reg.lastIndex = cx + 1;
                reg.exec(string);
                var index = reg.lastIndex;
                var s = string.slice(cx + 1, index - 1);
                create(s, o);
                marked.push(trimed.length);
                trimed.push(o);
                cx = reg.lastIndex;
                break;
            case "/":
                reg = reg5;
                o = 1;
            case "'":
                reg = reg || reg6;
                o = o || 2;
            case "\"":
                reg = reg || reg3;
                reg.lastIndex = cx + 1;
                do {
                    var s = reg.exec(string);
                } while (s && s[0].length === 2);
                var index = reg.lastIndex;
                s = string.slice(cx + 1, index - 1).replace(/\\([\s\S])/g, '$1');
                cx = index;
                if (o == 2) {
                    s = Symbol(s);
                } else if (o == 1) {
                    reg4.lastIndex = index;
                    var m = reg4.exec(string);
                    index = m.index;
                    var flag = string.slice(cx, index);
                    cx = index;
                    s = new RegExp(s, flag);
                }
                trimed.push(s);
                break;
            case "/":
                break;
            default:
                reg4.lastIndex = cx + 1;
                var match = reg4.exec(string);
                var index = match.index;

                var s = string.slice(cx, index);
                cx = index;
                switch (s) {
                    case "null":
                        s = null;
                        break;
                    case "true":
                        s = true;
                        break;
                    case "false":
                        s = false;
                        break;
                    default:
                        if (/^\d+[\/\-]/i.test(s)) {
                            s = new Date(s);
                        }
                        else if (/^\-?\d+$/.test(s)) {
                            if (s.length > 15) {
                                s = BigInt(s);
                            } else {
                                s = parseInt(s);
                            }
                        } else {
                            s = parseFloat(s);
                        }

                }
                trimed.push(s);
        }
    }
    var dist = trimed.slice(0, trimed.length);
    for (var cx = 0, dx = marked.length; cx < dx; cx++) {
        var index = marked[cx];
        var o = trimed[index];
        dist[index] = o instanceof Array ? [] : {};
    }
    for (var cx = 0, dx = marked.length; cx < dx; cx++) {
        var index = marked[cx];
        var o = trimed[index];
        var t = dist[index];
        var arr = o[""];
        delete o[""];
        for (var k in arr) {
            t[k] = dist[arr[k]];
        }
        for (var k in o) {
            var v = o[k];
            t[dist[k]] = dist[v];
        }
    }
    return dist[0];
}
module.exports = {
    stringify,
    parse(data) {
        if (!/^\s*([\[\{]|\[\s*\]|\{\s*\})/.test(data)) return parse(data);
        if (/^\s*\{[\d\,\:\s]*\}\s*,/.test(data)) return parse(data);
        if (/^\s*\[[\d\,\:\s]*\]\s*,/.test(data)) return parse(data);
        return JSON.parse(data);
    }
};<|endoftext|>


<commit_before>/**
 * @license
 * Patterns @VERSION@ parser - argument parser
 *
 * Copyright 2012 Simplon B.V.
 * Copyright 2012 Florian Friesdorf
 */
define([
    'jquery',
    '../logging'
], function($, logging) {
    var log = logging.getLogger('parser');

    function ArgumentParser(name) {
        this.params = [];
        this.defaults = {};
        this.attribute = "data-pat-" + name;
    }

    ArgumentParser.prototype = {
        named_param_pattern: /^\s*([a-zA-Z0-9\-]+)\s*:(.*)/,

        add_argument: function(name, default_value) {
            if (default_value === undefined)
                default_value = null;
            this.params.push(name);
            this.defaults[name] = default_value;
        },

        _parse: function(parameter) {
            if (typeof parameter==="number")
                parameter = parameter.toString();

            var opts = {}, i, name;

            if (parameter) {
                var parts = parameter.split(";"),
                    part, matches;

                // Grab all positional parameters
                i=-1;
                while (parts.length) {
                    i++;
                    if (i>=this.params.length) {
                        break;
                    }
                    part = parts.shift().trim();
                    if (!part)
                        continue;
                    if (this.named_param_pattern.test(part)) {
                        parts.unshift(part);
                        break;
                    }
                    opts[this.params[i]] = part.trim();
                }

                // Handle all named parameters
                for (i=0; i<parts.length; i++) {
                    if (!parts[i])
                        continue;

                    matches = parts[i].match(this.named_param_pattern);
                    if (!matches) {
                        log.warn("Positional parameters not allowed after named parameters");
                        break;
                    }
                    if (this.defaults[matches[1]] === undefined) {
                        log.warn("Unknown named parameter " + matches[1]);
                        continue;
                    }
                    opts[matches[1]] = matches[2].trim();
                }
            }

            // Resolve references
            for (name in opts) {
                var value = opts[name];

                if (typeof value==="string" && opts[name].slice(0,1) === "$")
                    opts[name]=opts[value.slice(1)];
            }

            return opts;
        },

        _coerce: function(data) {
            var i, name, value, types={};

            for (i in this.defaults)
                types[i]=this.defaults[i]===null ? "null" : typeof this.defaults[i];

            for (name in data) {
                value = data[name];

                if (typeof value !== types[name]) {
                    switch (types[name]) {
                        case "boolean":
                            if (typeof value === "string") {
                                value = value.toLowerCase();
                                var num = parseInt(value, 10);
                                if (!isNaN(num))
                                    value = !!num;
                                else
                                    value = (value==="true" || value==="y" || value==="yes" || value==="y");
                            } else if (typeof value === "number")
                                value = !!value;
                            else
                                log.warn("Cannot convert value for " + name + " to boolean");
                            break;
                        case "number":
                            if (typeof value === "string") {
                                value = parseInt(value, 10);
                                if (isNaN(value))
                                    log.warn("Cannot convert value for " + name + " to number");
                            } else if (typeof value === "boolean")
                                value = value + 0;
                            else
                                log.warn("Cannot convert value for " + name + " to number");
                            break;
                        case "null":  // Missing default values
                        case "undefined":
                            break;
                        default:
                            log.warn("Do not know how to convert value for " + name + " to " + types[name]);
                            break;
                    }
                }
                data[name]=value;
            }
            return data;
        },

        parse: function($el, defaults, multiple) {
            if (typeof defaults==="boolean" && multiple===undefined) {
                multiple=defaults;
                defaults={};
            }

            var stack = [[this.defaults]];
            if (typeof defaults==="object")
                stack.push([defaults]);

            var $parents = $el.parents(),
                i, data, final_length, frame, maxlen;
            for (i=$parents.length-1; i>=0; i--) {
                data = $parents.eq(i).attr(this.attribute);
                if (data) {
                    if (data.match(/&&/))
                        frame=data.split(/\s*&&\s*/).map(this._parse);
                    else
                        frame=[this._parse(data)];
                    maxlen = Math.max(frame.length, maxlen);
                    stack.push(frame);
                }
            }

            if (!multiple)
                final_length=1;

            var results=[], frame_length, x, xf;
            for (i=0; i<final_length; i++)
                results.push({});

            for (i=0; i<stack.length; i++) {
                frame=stack[i];
                frame_length=frame.length-1;

                for (x=0; x<final_length; x++) {
                    xf=(x>frame_length) ? frame_length : x;
                    results[i]=$.extend(results[x], frame[xf]);
                }
            }

            for (i=0; i<final_length; i++)
                results[i]=this._coerce(results[i]);
            return multiple ? results : results[0];
        }
    };

    return ArgumentParser;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>parser checks also $el for config<commit_after>/**
 * @license
 * Patterns @VERSION@ parser - argument parser
 *
 * Copyright 2012 Simplon B.V.
 * Copyright 2012 Florian Friesdorf
 */
define([
    'jquery',
    '../logging'
], function($, logging) {
    var log = logging.getLogger('parser');

    function ArgumentParser(name) {
        this.params = [];
        this.defaults = {};
        this.attribute = "data-pat-" + name;
    }

    ArgumentParser.prototype = {
        named_param_pattern: /^\s*([a-zA-Z0-9\-]+)\s*:(.*)/,

        add_argument: function(name, default_value) {
            if (default_value === undefined)
                default_value = null;
            this.params.push(name);
            this.defaults[name] = default_value;
        },

        _parse: function(parameter) {
            if (typeof parameter==="number")
                parameter = parameter.toString();

            var opts = {}, i, name;

            if (parameter) {
                var parts = parameter.split(";"),
                    part, matches;

                // Grab all positional parameters
                i=-1;
                while (parts.length) {
                    i++;
                    if (i>=this.params.length) {
                        break;
                    }
                    part = parts.shift().trim();
                    if (!part)
                        continue;
                    if (this.named_param_pattern.test(part)) {
                        parts.unshift(part);
                        break;
                    }
                    opts[this.params[i]] = part.trim();
                }

                // Handle all named parameters
                for (i=0; i<parts.length; i++) {
                    if (!parts[i])
                        continue;

                    matches = parts[i].match(this.named_param_pattern);
                    if (!matches) {
                        log.warn("Positional parameters not allowed after named parameters");
                        break;
                    }
                    if (this.defaults[matches[1]] === undefined) {
                        log.warn("Unknown named parameter " + matches[1]);
                        continue;
                    }
                    opts[matches[1]] = matches[2].trim();
                }
            }

            // Resolve references
            for (name in opts) {
                var value = opts[name];

                if (typeof value==="string" && opts[name].slice(0,1) === "$")
                    opts[name]=opts[value.slice(1)];
            }

            return opts;
        },

        _coerce: function(data) {
            var i, name, value, types={};

            for (i in this.defaults)
                types[i]=this.defaults[i]===null ? "null" : typeof this.defaults[i];

            for (name in data) {
                value = data[name];

                if (typeof value !== types[name]) {
                    switch (types[name]) {
                        case "boolean":
                            if (typeof value === "string") {
                                value = value.toLowerCase();
                                var num = parseInt(value, 10);
                                if (!isNaN(num))
                                    value = !!num;
                                else
                                    value = (value==="true" || value==="y" || value==="yes" || value==="y");
                            } else if (typeof value === "number")
                                value = !!value;
                            else
                                log.warn("Cannot convert value for " + name + " to boolean");
                            break;
                        case "number":
                            if (typeof value === "string") {
                                value = parseInt(value, 10);
                                if (isNaN(value))
                                    log.warn("Cannot convert value for " + name + " to number");
                            } else if (typeof value === "boolean")
                                value = value + 0;
                            else
                                log.warn("Cannot convert value for " + name + " to number");
                            break;
                        case "null":  // Missing default values
                        case "undefined":
                            break;
                        default:
                            log.warn("Do not know how to convert value for " + name + " to " + types[name]);
                            break;
                    }
                }
                data[name]=value;
            }
            return data;
        },

        parse: function($el, defaults, multiple) {
            if (typeof defaults==="boolean" && multiple===undefined) {
                multiple=defaults;
                defaults={};
            }

            var stack = [[this.defaults]];
            if (typeof defaults==="object")
                stack.push([defaults]);

            var $els = $el.parents().add($el),
                i, data, final_length, frame, maxlen;
            for (i=$els.length-1; i>=0; i--) {
                data = $els.eq(i).attr(this.attribute);
                if (data) {
                    if (data.match(/&&/))
                        frame=data.split(/\s*&&\s*/).map(this._parse);
                    else
                        frame=[this._parse(data)];
                    maxlen = Math.max(frame.length, maxlen);
                    stack.push(frame);
                }
            }

            if (!multiple)
                final_length=1;

            var results=[], frame_length, x, xf;
            for (i=0; i<final_length; i++)
                results.push({});

            for (i=0; i<stack.length; i++) {
                frame=stack[i];
                frame_length=frame.length-1;

                for (x=0; x<final_length; x++) {
                    xf=(x>frame_length) ? frame_length : x;
                    results[i]=$.extend(results[x], frame[xf]);
                }
            }

            for (i=0; i<final_length; i++)
                results[i]=this._coerce(results[i]);
            return multiple ? results : results[0];
        }
    };

    return ArgumentParser;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>import net from 'net';
import logger from 'winston';
import isEqual from 'lodash.isequal';
import {getRandomInt} from 'blinksocks-utils';
import {Config} from './config';
import {ClientProxy} from './client-proxy';
import {DNSCache} from './dns-cache';
import {Balancer} from './balancer';
import {Pipe} from './pipe';
import {Profile} from './profile';
import {
  MIDDLEWARE_DIRECTION_UPWARD,
  MIDDLEWARE_DIRECTION_DOWNWARD,
  createMiddleware
} from './middleware';

import {
  SOCKET_CONNECT_TO_DST,
  PROCESSING_FAILED
} from '../presets/defs';

import {
  UdpRequestMessage
} from '../proxies/socks5';

const dnsCache = DNSCache.create();

const TRACK_CHAR_UPLOAD = '↑';
const TRACK_CHAR_DOWNLOAD = '↓';
const TRACK_MAX_SIZE = 40;

let lastServer = null;

export class Socket {

  _id = null;

  _onClose = null;

  _isHandshakeDone = false;

  _remoteAddress = '';

  _remotePort = '';

  _bsocket = null;

  _fsocket = null;

  _pipe = null;

  _isRedirect = false; // server only

  _proxy = null; // client only

  // +---+-----------------------+---+
  // | C | d <--> u     u <--> d | S |
  // +---+-----------------------+---+
  _tracks = []; // [`target`, 'u', '20', 'u', '20', 'd', '10', ...]

  _timeout = 0;

  _timeout_timer = null;

  constructor({id, socket, onClose}) {
    this.onError = this.onError.bind(this);
    this.onBackwardSocketClose = this.onBackwardSocketClose.bind(this);
    this.onForwardSocketClose = this.onForwardSocketClose.bind(this);
    this.onForward = this.onForward.bind(this);
    this.onBackward = this.onBackward.bind(this);
    this._id = id;
    this._onClose = onClose;
    this._bsocket = socket;
    this._bsocket.on('error', this.onError);
    this._bsocket.on('close', this.onBackwardSocketClose);
    this._bsocket.on('data', this.onForward);
    this._remoteAddress = socket.remoteAddress;
    this._remotePort = socket.remotePort;
    if (__IS_SERVER__) {
      this._tracks.push(`${socket.remoteAddress}:${socket.remotePort}`);
      this.createPipe();
    } else {
      this._proxy = new ClientProxy({
        onHandshakeDone: this.onHandshakeDone.bind(this)
      });
    }
    this.setupTimeout();
  }

  // getters

  get id() {
    return this._id;
  }

  get remote() {
    return `${this._remoteAddress}:${this._remotePort}`;
  }

  // events

  onForward(buffer) {
    // reset timeout
    this._timeout = __TIMEOUT__;

    if (__IS_CLIENT__) {
      if (!this._proxy.isDone()) {
        // client handshake(multiple-protocols)
        this._proxy.makeHandshake(this._bsocket, buffer);
        return;
      }
      this.clientOut(buffer);
    } else {
      if (this._isRedirect) {
        // server redirect
        this._fsocket && this._fsocket.write(buffer);
        return;
      }
      this.serverIn(buffer);
    }
    Profile.totalIn += buffer.length;
  }

  onBackward(buffer) {
    // reset timeout
    this._timeout = __TIMEOUT__;

    if (__IS_CLIENT__) {
      this.clientIn(buffer);
    } else {
      if (this._isRedirect) {
        // server redirect
        this._bsocket && this._bsocket.write(buffer);
        return;
      }
      this.serverOut(buffer);
    }
  }

  onError(err) {
    logger.warn(`[socket] [${this.remote}] ${err.code} - ${err.message}`);
    Profile.errors += 1;
  }

  onForwardSocketClose() {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.destroy();
    }
    if (__IS_CLIENT__ && this._tracks.length > 0) {
      this.dumpTrack();
    }
    this._fsocket = null;
  }

  onBackwardSocketClose() {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.destroy();
    }
    this.onForwardSocketClose();
    if (__IS_SERVER__ && this._tracks.length > 0) {
      this.dumpTrack();
    }
    clearInterval(this._timeout_timer);
    this._bsocket = null;
    this._onClose(this); // notify hub to remove this one
  }

  /**
   * client handshake
   * @param addr
   * @param callback
   * @returns {Promise.<void>}
   */
  onHandshakeDone(addr, callback) {
    // select a server via Balancer
    const server = Balancer.getFastest();
    if (lastServer === null || !isEqual(server, lastServer)) {
      Config.initServer(server);
      lastServer = server;
      logger.info(`[balancer] use: ${__SERVER_HOST__}:${__SERVER_PORT__}`);
    }
    // connect to our server
    const [dstHost, dstPort] = [addr.host.toString(), addr.port.readUInt16BE(0)];
    return this.connect({host: __SERVER_HOST__, port: __SERVER_PORT__, dstHost, dstPort}, () => {
      this.createPipe(addr);
      this._tracks.push(`${dstHost}:${dstPort}`);
      this._isHandshakeDone = true;
      callback(this.onForward);
    });
  }

  // pipe chain

  clientOut(buffer) {
    let _buffer = buffer;

    // udp compatible
    if (this._socksUdpReady) {
      const request = UdpRequestMessage.parse(buffer);
      if (request !== null) {
        _buffer = request.DATA; // just drop RSV and FRAG
      } else {
        logger.warn(`[socket] [${this.remote}] -x-> dropped unidentified packet ${buffer.length} bytes`);
        return;
      }
    }

    if (this._fsocket !== null && !this._fsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_UPWARD, _buffer);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  serverIn(buffer) {
    if ((this._fsocket !== null && !this._fsocket.destroyed) || !this._isHandshakeDone) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_DOWNWARD, buffer);
        this._tracks.push(TRACK_CHAR_DOWNLOAD);
        this._tracks.push(buffer.length);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  serverOut(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_UPWARD, buffer);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  clientIn(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_DOWNWARD, buffer);
        this._tracks.push(TRACK_CHAR_DOWNLOAD);
        this._tracks.push(buffer.length);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  // fsocket and bsocket

  send(direction, buffer) {
    // reset timeout
    this._timeout = __TIMEOUT__;

    if (direction === MIDDLEWARE_DIRECTION_UPWARD) {
      if (__IS_CLIENT__) {
        this.clientForward(buffer);
      } else {
        this.serverBackward(buffer);
      }
    } else {
      if (__IS_CLIENT__) {
        this.clientBackward(buffer);
      } else {
        this.serverForward(buffer);
      }
    }
    Profile.totalOut += buffer.length;
  }

  clientForward(buffer) {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.write(buffer);
      this._tracks.push(TRACK_CHAR_UPLOAD);
      this._tracks.push(buffer.length);
    }
  }

  serverForward(buffer) {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.write(buffer);
    }
  }

  serverBackward(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.write(buffer);
      this._tracks.push(TRACK_CHAR_UPLOAD);
      this._tracks.push(buffer.length);
    }
  }

  clientBackward(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.write(buffer);
    }
  }

  /**
   * connect to a server, for both client and server
   * @param host
   * @param port
   * @param dstHost
   * @param dstPort
   * @param callback
   * @returns {Promise.<void>}
   */
  async connect({host, port, dstHost, dstPort}, callback) {
    // host could be empty, see https://github.com/blinksocks/blinksocks/issues/34
    if (host && port) {
      if (__IS_CLIENT__) {
        logger.info(`[socket] [${this.remote}] request: ${dstHost}:${dstPort}, connecting to: ${host}:${port}`);
      } else {
        logger.info(`[socket] [${this.remote}] connecting to: ${host}:${port}`);
      }
      this._tracks.push(`${host}:${port}`);
      try {
        const ip = await dnsCache.get(host);
        this._fsocket = net.connect({host: ip, port}, callback);
        this._fsocket.on('error', this.onError);
        this._fsocket.on('close', this.onForwardSocketClose);
        this._fsocket.on('data', this.onBackward);
      } catch (err) {
        logger.error(`[socket] [${this.remote}] connect to ${host}:${port} failed due to: ${err.message}`);
      }
    } else {
      logger.warn(`unexpected host=${host} port=${port}`);
      this.onBackwardSocketClose();
    }
  }

  // pipe

  /**
   * create pipes for both data forward and backward
   */
  createPipe(addr) {
    const presets = __PRESETS__.map(
      (preset, i) => createMiddleware(preset.name, {
        ...preset.params,
        ...(i === 0 ? addr : {})
      })
    );
    this._pipe = new Pipe({onNotified: this.onPipeNotified.bind(this)});
    this._pipe.setMiddlewares(MIDDLEWARE_DIRECTION_UPWARD, presets);
    this._pipe.on(`next_${MIDDLEWARE_DIRECTION_UPWARD}`, (buf) => this.send(MIDDLEWARE_DIRECTION_UPWARD, buf));
    this._pipe.on(`next_${MIDDLEWARE_DIRECTION_DOWNWARD}`, (buf) => this.send(MIDDLEWARE_DIRECTION_DOWNWARD, buf));
  }

  /**
   * if no action were caught by middlewares
   * @param action
   * @returns {*}
   */
  onPipeNotified(action) {
    if (__IS_SERVER__ && action.type === SOCKET_CONNECT_TO_DST) {
      const {targetAddress, onConnected} = action.payload;
      return this.connect(targetAddress, () => {
        this._isHandshakeDone = true;
        onConnected();
      });
    }
    if (action.type === PROCESSING_FAILED) {
      return this.onPresetFailed(action);
    }
  }

  /**
   * if any preset failed, this function will be called
   * @param action
   */
  onPresetFailed(action) {
    const {message, orgData} = action.payload;
    if (__IS_SERVER__ && __REDIRECT__ !== '' && this._fsocket === null) {
      const [host, port] = __REDIRECT__.split(':');
      logger.error(`[socket] [${this.remote}] connection is redirected to ${host}:${port} due to: ${message}`);
      this.connect({host, port}, () => {
        this._isRedirect = true;
        this._fsocket.write(orgData);
      });
    } else {
      const timeout = getRandomInt(10, 40);
      logger.error(`[socket] [${this.remote}] connection will be closed in ${timeout}s due to: ${message}`);
      setTimeout(() => {
        this.onForwardSocketClose();
        this.onBackwardSocketClose();
      }, timeout * 1e3);
    }
    Profile.fatals += 1;
  }

  // methods

  /**
   * initialize timeout
   */
  setupTimeout() {
    this._timeout = __TIMEOUT__;
    this._timeout_timer = setInterval(() => {
      if (--this._timeout < 1) {
        logger.warn(`[socket] [${this.remote}] timeout: no I/O on the connection for ${__TIMEOUT__}s`);
        this.onForwardSocketClose();
        this.onBackwardSocketClose();
      }
    }, 1e3);
  }

  /**
   * print connection track string, and only record the
   * leading and the trailing TRACK_MAX_SIZE / 2
   */
  dumpTrack() {
    let strs = [];
    let dp = 0, db = 0;
    let up = 0, ub = 0;
    let ud = '';
    for (const el of this._tracks) {
      if (el === TRACK_CHAR_UPLOAD || el === TRACK_CHAR_DOWNLOAD) {
        if (ud === el) {
          continue;
        }
        ud = el;
      }
      if (Number.isInteger(el)) {
        if (ud === TRACK_CHAR_DOWNLOAD) {
          dp += 1;
          db += el;
        }
        if (ud === TRACK_CHAR_UPLOAD) {
          up += 1;
          ub += el;
        }
      }
      strs.push(el);
    }
    const perSize = Math.floor(TRACK_MAX_SIZE / 2);
    if (strs.length > TRACK_MAX_SIZE) {
      strs = strs.slice(0, perSize).concat([' ... ']).concat(strs.slice(-perSize));
    }
    const summary = __IS_CLIENT__ ? `out/in = ${up}/${dp}, ${ub}b/${db}b` : `in/out = ${dp}/${up}, ${db}b/${ub}b`;
    logger.info(`[socket] [${this.remote}] closed with summary(${summary}) abstract(${strs.join(' ')})`);
    this._tracks = [];
  }

  /**
   * close both sides
   */
  destroy() {
    this.onForwardSocketClose();
    this.onBackwardSocketClose();
  }

}
<commit_msg>refactor(socket): use socket.setTimeout() instead of setInterval()<commit_after>import net from 'net';
import logger from 'winston';
import isEqual from 'lodash.isequal';
import {getRandomInt} from 'blinksocks-utils';
import {Config} from './config';
import {ClientProxy} from './client-proxy';
import {DNSCache} from './dns-cache';
import {Balancer} from './balancer';
import {Pipe} from './pipe';
import {Profile} from './profile';
import {
  MIDDLEWARE_DIRECTION_UPWARD,
  MIDDLEWARE_DIRECTION_DOWNWARD,
  createMiddleware
} from './middleware';

import {
  SOCKET_CONNECT_TO_DST,
  PROCESSING_FAILED
} from '../presets/defs';

import {
  UdpRequestMessage
} from '../proxies/socks5';

const dnsCache = DNSCache.create();

const TRACK_CHAR_UPLOAD = '↑';
const TRACK_CHAR_DOWNLOAD = '↓';
const TRACK_MAX_SIZE = 40;

let lastServer = null;

export class Socket {

  _id = null;

  _onClose = null;

  _isHandshakeDone = false;

  _remoteAddress = '';

  _remotePort = '';

  _bsocket = null;

  _fsocket = null;

  _pipe = null;

  _isRedirect = false; // server only

  _proxy = null; // client only

  // +---+-----------------------+---+
  // | C | d <--> u     u <--> d | S |
  // +---+-----------------------+---+
  _tracks = []; // [`target`, 'u', '20', 'u', '20', 'd', '10', ...]

  constructor({id, socket, onClose}) {
    this.onError = this.onError.bind(this);
    this.onBackwardSocketTimeout = this.onBackwardSocketTimeout.bind(this);
    this.onForwardSocketTimeout = this.onForwardSocketTimeout.bind(this);
    this.onBackwardSocketClose = this.onBackwardSocketClose.bind(this);
    this.onForwardSocketClose = this.onForwardSocketClose.bind(this);
    this.onForward = this.onForward.bind(this);
    this.onBackward = this.onBackward.bind(this);
    this._id = id;
    this._onClose = onClose;
    this._remoteAddress = socket.remoteAddress;
    this._remotePort = socket.remotePort;
    this._bsocket = socket;
    this._bsocket.on('error', this.onError);
    this._bsocket.on('close', this.onBackwardSocketClose);
    this._bsocket.on('timeout', this.onBackwardSocketTimeout.bind(this, {
      host: this._remoteAddress,
      port: this._remotePort
    }));
    this._bsocket.on('data', this.onForward);
    this._bsocket.setTimeout(__TIMEOUT__ * 1e3);
    if (__IS_SERVER__) {
      this._tracks.push(`${this._remoteAddress}:${this._remotePort}`);
      this.createPipe();
    } else {
      this._proxy = new ClientProxy({
        onHandshakeDone: this.onHandshakeDone.bind(this)
      });
    }
  }

  // getters

  get id() {
    return this._id;
  }

  get remote() {
    return `${this._remoteAddress}:${this._remotePort}`;
  }

  // events

  onForward(buffer) {
    if (__IS_CLIENT__) {
      if (!this._proxy.isDone()) {
        // client handshake(multiple-protocols)
        this._proxy.makeHandshake(this._bsocket, buffer);
        return;
      }
      this.clientOut(buffer);
    } else {
      if (this._isRedirect) {
        // server redirect
        this._fsocket && this._fsocket.write(buffer);
        return;
      }
      this.serverIn(buffer);
    }
    Profile.totalIn += buffer.length;
  }

  onBackward(buffer) {
    if (__IS_CLIENT__) {
      this.clientIn(buffer);
    } else {
      if (this._isRedirect) {
        // server redirect
        this._bsocket && this._bsocket.write(buffer);
        return;
      }
      this.serverOut(buffer);
    }
  }

  onError(err) {
    logger.warn(`[socket] [${this.remote}] ${err.code} - ${err.message}`);
    Profile.errors += 1;
  }

  onForwardSocketTimeout({host, port}) {
    logger.warn(`[socket] [${host}:${port}] timeout: no I/O on the connection for ${__TIMEOUT__}s`);
    this.onForwardSocketClose();
  }

  onForwardSocketClose() {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.destroy();
    }
    if (__IS_CLIENT__ && this._tracks.length > 0) {
      this.dumpTrack();
    }
    this._fsocket = null;
  }

  onBackwardSocketTimeout({host, port}) {
    logger.warn(`[socket] [${host}:${port}] timeout: no I/O on the connection for ${__TIMEOUT__}s`);
    this.onBackwardSocketClose();
  }

  onBackwardSocketClose() {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.destroy();
    }
    // NOTE: close forward socket when backward socket is closed, does it matter?
    this.onForwardSocketClose();
    if (__IS_SERVER__ && this._tracks.length > 0) {
      this.dumpTrack();
    }
    this._bsocket = null;
    this._onClose(this); // notify hub to remove this one
  }

  /**
   * client handshake
   * @param addr
   * @param callback
   * @returns {Promise.<void>}
   */
  onHandshakeDone(addr, callback) {
    // select a server via Balancer
    const server = Balancer.getFastest();
    if (lastServer === null || !isEqual(server, lastServer)) {
      Config.initServer(server);
      lastServer = server;
      logger.info(`[balancer] use: ${__SERVER_HOST__}:${__SERVER_PORT__}`);
    }
    // connect to our server
    const [dstHost, dstPort] = [addr.host.toString(), addr.port.readUInt16BE(0)];
    return this.connect({host: __SERVER_HOST__, port: __SERVER_PORT__, dstHost, dstPort}, () => {
      this.createPipe(addr);
      this._tracks.push(`${dstHost}:${dstPort}`);
      this._isHandshakeDone = true;
      callback(this.onForward);
    });
  }

  // pipe chain

  clientOut(buffer) {
    let _buffer = buffer;

    // udp compatible
    if (this._socksUdpReady) {
      const request = UdpRequestMessage.parse(buffer);
      if (request !== null) {
        _buffer = request.DATA; // just drop RSV and FRAG
      } else {
        logger.warn(`[socket] [${this.remote}] -x-> dropped unidentified packet ${buffer.length} bytes`);
        return;
      }
    }

    if (this._fsocket !== null && !this._fsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_UPWARD, _buffer);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  serverIn(buffer) {
    if ((this._fsocket !== null && !this._fsocket.destroyed) || !this._isHandshakeDone) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_DOWNWARD, buffer);
        this._tracks.push(TRACK_CHAR_DOWNLOAD);
        this._tracks.push(buffer.length);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  serverOut(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_UPWARD, buffer);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  clientIn(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      try {
        this._pipe.feed(MIDDLEWARE_DIRECTION_DOWNWARD, buffer);
        this._tracks.push(TRACK_CHAR_DOWNLOAD);
        this._tracks.push(buffer.length);
      } catch (err) {
        logger.error(`[socket] [${this.remote}]`, err);
      }
    }
  }

  // fsocket and bsocket

  send(direction, buffer) {
    if (direction === MIDDLEWARE_DIRECTION_UPWARD) {
      if (__IS_CLIENT__) {
        this.clientForward(buffer);
      } else {
        this.serverBackward(buffer);
      }
    } else {
      if (__IS_CLIENT__) {
        this.clientBackward(buffer);
      } else {
        this.serverForward(buffer);
      }
    }
    Profile.totalOut += buffer.length;
  }

  clientForward(buffer) {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.write(buffer);
      this._tracks.push(TRACK_CHAR_UPLOAD);
      this._tracks.push(buffer.length);
    }
  }

  serverForward(buffer) {
    if (this._fsocket !== null && !this._fsocket.destroyed) {
      this._fsocket.write(buffer);
    }
  }

  serverBackward(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.write(buffer);
      this._tracks.push(TRACK_CHAR_UPLOAD);
      this._tracks.push(buffer.length);
    }
  }

  clientBackward(buffer) {
    if (this._bsocket !== null && !this._bsocket.destroyed) {
      this._bsocket.write(buffer);
    }
  }

  /**
   * connect to a server, for both client and server
   * @param host
   * @param port
   * @param dstHost
   * @param dstPort
   * @param callback
   * @returns {Promise.<void>}
   */
  async connect({host, port, dstHost, dstPort}, callback) {
    // host could be empty, see https://github.com/blinksocks/blinksocks/issues/34
    if (host && port) {
      if (__IS_CLIENT__) {
        logger.info(`[socket] [${this.remote}] request: ${dstHost}:${dstPort}, connecting to: ${host}:${port}`);
      } else {
        logger.info(`[socket] [${this.remote}] connecting to: ${host}:${port}`);
      }
      this._tracks.push(`${host}:${port}`);
      try {
        const ip = await dnsCache.get(host);
        this._fsocket = net.connect({host: ip, port}, callback);
        this._fsocket.on('error', this.onError);
        this._fsocket.on('close', this.onForwardSocketClose);
        this._fsocket.on('timeout', this.onForwardSocketTimeout.bind(this, {host, port}));
        this._fsocket.on('data', this.onBackward);
        this._fsocket.setTimeout(__TIMEOUT__ * 1e3);
      } catch (err) {
        logger.error(`[socket] [${this.remote}] connect to ${host}:${port} failed due to: ${err.message}`);
      }
    } else {
      logger.warn(`unexpected host=${host} port=${port}`);
      this.onBackwardSocketClose();
    }
  }

  // pipe

  /**
   * create pipes for both data forward and backward
   */
  createPipe(addr) {
    const presets = __PRESETS__.map(
      (preset, i) => createMiddleware(preset.name, {
        ...preset.params,
        ...(i === 0 ? addr : {})
      })
    );
    this._pipe = new Pipe({onNotified: this.onPipeNotified.bind(this)});
    this._pipe.setMiddlewares(MIDDLEWARE_DIRECTION_UPWARD, presets);
    this._pipe.on(`next_${MIDDLEWARE_DIRECTION_UPWARD}`, (buf) => this.send(MIDDLEWARE_DIRECTION_UPWARD, buf));
    this._pipe.on(`next_${MIDDLEWARE_DIRECTION_DOWNWARD}`, (buf) => this.send(MIDDLEWARE_DIRECTION_DOWNWARD, buf));
  }

  /**
   * if no action were caught by middlewares
   * @param action
   * @returns {*}
   */
  onPipeNotified(action) {
    if (__IS_SERVER__ && action.type === SOCKET_CONNECT_TO_DST) {
      const {targetAddress, onConnected} = action.payload;
      return this.connect(targetAddress, () => {
        this._isHandshakeDone = true;
        onConnected();
      });
    }
    if (action.type === PROCESSING_FAILED) {
      return this.onPresetFailed(action);
    }
  }

  /**
   * if any preset failed, this function will be called
   * @param action
   */
  onPresetFailed(action) {
    const {message, orgData} = action.payload;
    if (__IS_SERVER__ && __REDIRECT__ !== '' && this._fsocket === null) {
      const [host, port] = __REDIRECT__.split(':');
      logger.error(`[socket] [${this.remote}] connection is redirected to ${host}:${port} due to: ${message}`);
      this.connect({host, port}, () => {
        this._isRedirect = true;
        this._fsocket.write(orgData);
      });
    } else {
      const timeout = getRandomInt(10, 40);
      logger.error(`[socket] [${this.remote}] connection will be closed in ${timeout}s due to: ${message}`);
      setTimeout(() => {
        this.onForwardSocketClose();
        this.onBackwardSocketClose();
      }, timeout * 1e3);
    }
    Profile.fatals += 1;
  }

  // methods

  /**
   * print connection track string, and only record the
   * leading and the trailing TRACK_MAX_SIZE / 2
   */
  dumpTrack() {
    let strs = [];
    let dp = 0, db = 0;
    let up = 0, ub = 0;
    let ud = '';
    for (const el of this._tracks) {
      if (el === TRACK_CHAR_UPLOAD || el === TRACK_CHAR_DOWNLOAD) {
        if (ud === el) {
          continue;
        }
        ud = el;
      }
      if (Number.isInteger(el)) {
        if (ud === TRACK_CHAR_DOWNLOAD) {
          dp += 1;
          db += el;
        }
        if (ud === TRACK_CHAR_UPLOAD) {
          up += 1;
          ub += el;
        }
      }
      strs.push(el);
    }
    const perSize = Math.floor(TRACK_MAX_SIZE / 2);
    if (strs.length > TRACK_MAX_SIZE) {
      strs = strs.slice(0, perSize).concat([' ... ']).concat(strs.slice(-perSize));
    }
    const summary = __IS_CLIENT__ ? `out/in = ${up}/${dp}, ${ub}b/${db}b` : `in/out = ${dp}/${up}, ${db}b/${ub}b`;
    logger.info(`[socket] [${this.remote}] closed with summary(${summary}) abstract(${strs.join(' ')})`);
    this._tracks = [];
  }

  /**
   * close both sides
   */
  destroy() {
    this.onForwardSocketClose();
    this.onBackwardSocketClose();
  }

}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Setup nodejs-like 'global' on web
var global = global || this;

// Top-Level of foam package
foam = { array: {}, fn: {}, string: {}, util: {} };

foam.fn.memoize1 = function memoize1(f) {
  // Faster version of memoize() when only dealing with one argument.
  var cache = {};
  var g = function(arg) {
    console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
    var key = arg ? arg.toString() : '';
    if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
    return cache[key];
  };
  foam.fn.setName(g, 'memoize1(' + f.name + ')');
  return g;
};

foam.fn.setName = function setName(f, name) {
  // Set a function's name for improved debugging and profiling
  //Object.defineProperty(f, 'name', {value: name, configurable: true}); //TODO: re-enable if supported
};

/** convenience method to append 'arguments' onto a real array */
foam.fn.appendArguments = function appendArguments(a, args, start) {
  for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
  return a;
},
/** convenience method to turn 'arguments' into a real array */
foam.fn.argsToArray = function argsToArray(args) {
  return foam.fn.appendArguments([], args, 0);
},

foam.string.constantize = foam.fn.memoize1(function(str) {
  // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
  return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
    return a.substring(0,1) + '_' + a.substring(1,2);
  }).toUpperCase();
});

foam.string.pad = function(str, size) {
  // Right pads to size if size > 0, Left pads to -size if size < 0
  return size < 0 ?
    (new Array(-size).join(' ') + str).slice(size)       :
    (str + new Array(size).join(' ')).substring(0, size) ;
};

foam.events = {

  oneTime: function oneTime(listener) {
    /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
    return function() {
      listener.apply(this, foam.fn.argsToArray(arguments));
      arguments[2](); // the unsubscribe fn
    };
  },

  consoleLog: function consoleLog(listener) {
    /** Log all listener invocations to console. **/
    return function() {
      var args = foam.fn.argsToArray(arguments);
      console.log(args);

      listener.apply(this, args);
    };
  },

  Observable: {
    create: function(name, src) {
      var o = Object.create(this);
      o.name = name;
      o.src  = src;
      return o;
    },

    notifyListeners_: function(node, args) {
      var count = 0;
      while ( node && node.next ) {
        node = node.next;
        args[0] = node.sub;
        node.l.apply(null, args);
        count++;
      }
      return count;
    },

    pub: function(opt_topic /*, args */) {
      /* Return number of listeners notified. */
      if ( opt_topic ) {
        if ( ! this.next && ( ! this.topics_ || ! this.topics_[opt_topic] ) ) return 0;
      } else {
        if ( ! this.next ) return 0;
      }

      var args      = [null];
      var topicSubs = opt_topic && this.topics_ && this.topics_[opt_topic];

      args.push.apply(args, arguments);

      return this.notifyListeners_(topicSubs, args) +
        this.notifyListeners_(this, args);
    },

    sub: function(l, opt_topic) {
      var subs;
      if ( opt_topic ) {
        var topics = this.topics_ || ( this.topics_ = {} );
        subs = topics[opt_topic] || (topics[opt_topic] = {});
      } else {
        subs = this;
      }

      var node = {
        prev: subs,
        next: subs.next,
        l: l,
        sub: { src: this.src, topic: this }
      };
      subs.next = node;

      node.sub.destroy = function() {
        if ( this.next ) this.next.prev = this.prev;
        if ( this.prev ) this.prev.next = this.next;
      }.bind(node);

      return node.sub;
    },

    unsub: function(l, opt_topic) {
      var subs;
      if ( opt_topic ) {
        if ( ! this.topics_ ) return;
        subs = topics[opt_topic];
        if ( ! subs ) return;
      } else {
        subs = this;
      }

      while ( subs.next ) {
        if ( subs.next === l ) {
          subs.next = subs.next.next;
          if ( subs.next ) subs.next.prev = subs;
          return;
        }
      }
    },

    destroy: function() {
      // TODO: better to walk and disconnect linked-lists in case
      // external objects have references to subscriptions.
      this.next = this.topics_ = undefined;
    },

    toString: function() {
      return 'Topic(' + this.name + ')';
    }
  }

};
<commit_msg>Subscription.destroy() disconnects to avoid issues if called more than once.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Setup nodejs-like 'global' on web
var global = global || this;

// Top-Level of foam package
foam = { array: {}, fn: {}, string: {}, util: {} };

foam.fn.memoize1 = function memoize1(f) {
  // Faster version of memoize() when only dealing with one argument.
  var cache = {};
  var g = function(arg) {
    console.assert(arguments.length == 1, "Memoize1'ed functions must take exactly one argument.");
    var key = arg ? arg.toString() : '';
    if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg);
    return cache[key];
  };
  foam.fn.setName(g, 'memoize1(' + f.name + ')');
  return g;
};

foam.fn.setName = function setName(f, name) {
  // Set a function's name for improved debugging and profiling
  //Object.defineProperty(f, 'name', {value: name, configurable: true}); //TODO: re-enable if supported
};

/** convenience method to append 'arguments' onto a real array */
foam.fn.appendArguments = function appendArguments(a, args, start) {
  for ( var i = start ; i < args.length ; i++ ) a.push(args[i]);
  return a;
},
/** convenience method to turn 'arguments' into a real array */
foam.fn.argsToArray = function argsToArray(args) {
  return foam.fn.appendArguments([], args, 0);
},

foam.string.constantize = foam.fn.memoize1(function(str) {
  // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT
  return str.replace(/[a-z][^0-9a-z_]/g, function(a) {
    return a.substring(0,1) + '_' + a.substring(1,2);
  }).toUpperCase();
});

foam.string.pad = function(str, size) {
  // Right pads to size if size > 0, Left pads to -size if size < 0
  return size < 0 ?
    (new Array(-size).join(' ') + str).slice(size)       :
    (str + new Array(size).join(' ')).substring(0, size) ;
};

foam.events = {

  oneTime: function oneTime(listener) {
    /** Create a "one-time" listener which unsubscribes itself after its first invocation. **/
    return function() {
      listener.apply(this, foam.fn.argsToArray(arguments));
      arguments[2](); // the unsubscribe fn
    };
  },

  consoleLog: function consoleLog(listener) {
    /** Log all listener invocations to console. **/
    return function() {
      var args = foam.fn.argsToArray(arguments);
      console.log(args);

      listener.apply(this, args);
    };
  },

  Observable: {
    create: function(name, src) {
      var o = Object.create(this);
      o.name = name;
      o.src  = src;
      return o;
    },

    notifyListeners_: function(node, args) {
      var count = 0;
      while ( node && node.next ) {
        node = node.next;
        args[0] = node.sub;
        node.l.apply(null, args);
        count++;
      }
      return count;
    },

    pub: function(opt_topic /*, args */) {
      /* Return number of listeners notified. */
      if ( opt_topic ) {
        if ( ! this.next && ( ! this.topics_ || ! this.topics_[opt_topic] ) ) return 0;
      } else {
        if ( ! this.next ) return 0;
      }

      var args      = [null];
      var topicSubs = opt_topic && this.topics_ && this.topics_[opt_topic];

      args.push.apply(args, arguments);

      return this.notifyListeners_(topicSubs, args) +
        this.notifyListeners_(this, args);
    },

    sub: function(l, opt_topic) {
      var subs;
      if ( opt_topic ) {
        var topics = this.topics_ || ( this.topics_ = {} );
        subs = topics[opt_topic] || (topics[opt_topic] = {});
      } else {
        subs = this;
      }

      var node = {
        prev: subs,
        next: subs.next,
        l: l,
        sub: { src: this.src, topic: this }
      };
      subs.next = node;

      node.sub.destroy = function() {
        if ( node.next ) node.next.prev = node.prev;
        if ( node.prev ) node.prev.next = node.next;

        // Disconnect so that calling destroy more than once is harmless
        this.next = this.prev = null;
      };

      return node.sub;
    },

    unsub: function(l, opt_topic) {
      var subs;
      if ( opt_topic ) {
        if ( ! this.topics_ ) return;
        subs = topics[opt_topic];
        if ( ! subs ) return;
      } else {
        subs = this;
      }

      while ( subs.next ) {
        if ( subs.next === l ) {
          subs.next = subs.next.next;
          if ( subs.next ) subs.next.prev = subs;
          return;
        }
      }
    },

    destroy: function() {
      // TODO: better to walk and disconnect linked-lists in case
      // external objects have references to subscriptions.
      this.next = this.topics_ = undefined;
    },

    toString: function() {
      return 'Topic(' + this.name + ')';
    }
  }
};
<|endoftext|>


<commit_before>import {omit} from "lodash";
import PouchDB from 'pouchdb';
import getCouchOptions from './get-couch-options';

class CouchStore {
  constructor(client) {
    this.client = client;
  }

  async add(sid) {
    const exists = await this.exists(sid);

    if (!exists) {
      return await this.client.post({
        _id: sid,
        created: new Date()
      });
    }
  }

  async _get(sid) {
    const res = await this.client.allDocs({
      key: sid,
      limit: 1
    });

    return res.rows.length && res.rows[0].value;
  }

  async exists(sid) {
    return !!await this._get(sid);
  }

  async remove(sid) {
    const session = await this._get(sid);

    return await this.client.remove(sid, session.rev);
  }
}

export default async function createCouchStore(sessOpts, couchOpts) {
  const options = {
    db: "jwt_sessions",
    ...couchOpts,
    ...getCouchOptions(sessOpts)
  };

  const client = await new Promise((resolve, reject) => {
    const ret = new PouchDB(
      `${options.baseUrl}/${options.db}`,
      omit(options, "baseUrl", "db"),
      err => err ? reject(err) : resolve(ret)
    );
  });

  return new CouchStore(client);
}
<commit_msg>fix: remove PouchDB callback deprecation notice<commit_after>import {omit} from "lodash";
import PouchDB from 'pouchdb';
import getCouchOptions from './get-couch-options';

class CouchStore {
  constructor(client) {
    this.client = client;
  }

  async add(sid) {
    const exists = await this.exists(sid);

    if (!exists) {
      return await this.client.post({
        _id: sid,
        created: new Date()
      });
    }
  }

  async _get(sid) {
    const res = await this.client.allDocs({
      key: sid,
      limit: 1
    });

    return res.rows.length && res.rows[0].value;
  }

  async exists(sid) {
    return !!await this._get(sid);
  }

  async remove(sid) {
    const session = await this._get(sid);

    return await this.client.remove(sid, session.rev);
  }
}

export default async function createCouchStore(sessOpts, couchOpts) {
  const options = {
    db: "jwt_sessions",
    ...couchOpts,
    ...getCouchOptions(sessOpts)
  };

  return new CouchStore(new PouchDB(`${options.baseUrl}/${options.db}`, omit(options, "baseUrl", "db")));
}
<|endoftext|>


<commit_before>'use strict';

angular.module('angular-cron-jobs').factory('cronService', function() {
    var service = {};

    service.setCron = function(n) {
        //  console.log('set cron called: ', n);
        var cron = ['*', '*', '*',  '*',  '*'];

        if(n && n.base && n.base >= 2) {
            cron[0] = typeof n.minuteValue !== undefined ? n.minuteValue : '*';
        }

        if(n && n.base && n.base >= 3) {
            cron[1] = typeof n.hourValue !== undefined ? n.hourValue  : '*';
        }

        if(n && n.base && n.base === 4) {
            cron[4] = n.dayValue;
        }

        if(n && n.base && n.base >= 5) {
            cron[2] = typeof n.dayOfMonthValue !== undefined ? n.dayOfMonthValue : '*';
        }

        if(n && n.base && n.base === 6) {
            cron[3] = typeof n.monthValue !== undefined ? n.monthValue : '*';
        }
        //  console.log('cron after setCron ', cron.join(' '));
        return cron.join(' ');
    };

    service.fromCron = function(value) {
        //  console.log('set cron fired!');
       var cron = value.replace(/\s+/g, ' ').split(' ');
       var frequency = {base: '1'}; // default: every minute

       if(cron[1] === '*' && cron[2] === '*' && cron[3] === '*'  && cron[4] === '*') {
           frequency.base = 2; // every hour
       } else if(cron[2] === '*' && cron[3] === '*'  && cron[4] === '*') {
           frequency.base = 3; // every day
       } else if(cron[2] === '*' && cron[3] === '*') {
           frequency.base = 4; // every week
       } else if(cron[3] === '*' && cron[4] === '*') {
           frequency.base = 5; // every month
       } else if(cron[4] === '*') {
           frequency.base = 6; // every year
       }

       // console.log('frequency should be 5: ', frequency, cron);

       if (cron[0] !== '*') {
           frequency.minuteValue = parseInt(cron[0]);
       }
       if (cron[1] !== '*') {
           frequency.hourValue = parseInt(cron[1]);
       }
       if (cron[2] !== '*') {
           frequency.dayOfMonthValue = parseInt(cron[2]);
       }
       if (cron[3] !== '*') {
           frequency.monthValue = parseInt(cron[3]);
       }
       if (cron[4] !== '*') {
           frequency.dayValue = parseInt(cron[4]);
       }

       //frequency.base += ''; // 'cast' to string in order to set proper value on "every" modal

       // console.log('freq ', frequency);
       return frequency;
   };

   return service;
});
<commit_msg>fixed bug that cause * * * * * for server init to default to hour syntax<commit_after>'use strict';

angular.module('angular-cron-jobs').factory('cronService', function() {
    var service = {};

    service.setCron = function(n) {
        //  console.log('set cron called: ', n);
        var cron = ['*', '*', '*',  '*',  '*'];

        if(n && n.base && n.base >= 2) {
            cron[0] = typeof n.minuteValue !== undefined ? n.minuteValue : '*';
        }

        if(n && n.base && n.base >= 3) {
            cron[1] = typeof n.hourValue !== undefined ? n.hourValue  : '*';
        }

        if(n && n.base && n.base === 4) {
            cron[4] = n.dayValue;
        }

        if(n && n.base && n.base >= 5) {
            cron[2] = typeof n.dayOfMonthValue !== undefined ? n.dayOfMonthValue : '*';
        }

        if(n && n.base && n.base === 6) {
            cron[3] = typeof n.monthValue !== undefined ? n.monthValue : '*';
        }
        //  console.log('cron after setCron ', cron.join(' '));
        return cron.join(' ');
    };

    service.fromCron = function(value) {
        //  console.log('set cron fired!');
       var cron = value.replace(/\s+/g, ' ').split(' ');
       var frequency = {base: '1'}; // default: every minute

       if(cron[0] === '*' && cron[1] === '*' && cron[2] === '*' && cron[3] === '*'  && cron[4] === '*') {
           frequency.base = 1; // every minute
       } else if(cron[1] === '*' && cron[2] === '*' && cron[3] === '*'  && cron[4] === '*') {
           frequency.base = 2; // every hour
       } else if(cron[2] === '*' && cron[3] === '*'  && cron[4] === '*') {
           frequency.base = 3; // every day
       } else if(cron[2] === '*' && cron[3] === '*') {
           frequency.base = 4; // every week
       } else if(cron[3] === '*' && cron[4] === '*') {
           frequency.base = 5; // every month
       } else if(cron[4] === '*') {
           frequency.base = 6; // every year
       }

       // console.log('frequency should be 5: ', frequency, cron);

       if (cron[0] !== '*') {
           frequency.minuteValue = parseInt(cron[0]);
       }
       if (cron[1] !== '*') {
           frequency.hourValue = parseInt(cron[1]);
       }
       if (cron[2] !== '*') {
           frequency.dayOfMonthValue = parseInt(cron[2]);
       }
       if (cron[3] !== '*') {
           frequency.monthValue = parseInt(cron[3]);
       }
       if (cron[4] !== '*') {
           frequency.dayValue = parseInt(cron[4]);
       }

       //frequency.base += ''; // 'cast' to string in order to set proper value on "every" modal

       // console.log('freq ', frequency);
       return frequency;
   };

   return service;
});
<|endoftext|>


<commit_before>'use strict';

var http = require('http');

var Response = function(response) {

  this.ok = function(result) {
    result = result || {};

    if(typeof result.success == 'undefined') {
      result.success = true;
    }

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    return response.end(JSON.stringify(result));
  };

  this.forbidden = function() {
    response.writeHead(403);
    return response.end('Forbidden.');
  };

};

var Server = function(port, requestFunction) {

  var _server = http.createServer(function(request, response) {

    response = new Response(response);

    if(request.method == 'POST') {
      var body = '';
      request.on('data', function (data) {
        body += data;
        // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
        if (body.length > 1e6) {
          // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST
          request.connection.destroy();
        }
      });
      request.on('end', function () {
        requestFunction(request, response, body);
      });
    }
    else {
      requestFunction(request, response);
    }

  }).listen(port);

  this.http = function() {
    return _server;
  };

};

module.exports = Server;<commit_msg>feat(server): create flood response<commit_after>'use strict';

var http = require('http');

var Response = function(response) {

  this.ok = function(result) {
    result = result || {};
    result.success = true;

    response.writeHead(200, {
      'Content-Type': 'text/plain'
    });
    return response.end(JSON.stringify(result));
  };

  this.forbidden = function() {
    response.writeHead(403);
    return response.end('Forbidden.');
  };

  this.flood = function() {
    response.writeHead(418);
    return response.end('');
  };

};

var Server = function(port, requestFunction) {

  var _server = http.createServer(function(request, response) {

    response = new Response(response);

    var flood = false;

    if(request.method == 'POST') {
      var body = '';
      request.on('data', function (data) {
        if(flood) {
          return;
        }

        body += data;
        // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
        if (body.length > 1e6) {
          // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST
          flood = true;
          body = '';
        }
      });
      request.on('end', function () {
        if(flood) {
          return response.flood();
        }

        requestFunction(request, response, body);
      });
    }
    else {
      requestFunction(request, response);
    }

  }).listen(port);

  this.http = function() {
    return _server;
  };

};

module.exports = Server;<|endoftext|>


<commit_before>const CronJob = require('cron').CronJob
import https from 'https'
import fs from 'fs'
import Promise from 'bluebird'

import Ebook from '../models/Ebook'
import schedules from './schedules.js'
import createEbook from '../news-parser'
import uploadToS3 from '../helpers/upload-to-s3'
//Quan s'inicia el server mira la programació establerta a schedules.js i crea 1 cronjob per cada parser
let timer = 6
function scheduleBundles(){
  schedules.forEach((schedule) => {
    // timer = timer + 5;
    timer > 50  ? timer = 0 : ''
    const source = schedule.source
    const hourFreq = schedule.hourFreq
    const timeZone = schedule.timeZone
    const cronTime = `00 ${timer} ${hourFreq} * * *` // se creara el bundle cada horafreq horas, con un desfase de timer minutos
    new CronJob({
      cronTime: cronTime,
      onTick: function() {
      // variables declaration
        var ebookData
        var ebookSize
        var ebookSizeDB
      // create ebook
        createEbook([source])
        .then((data) => {
          ebookData = data
          console.log(ebookData);
        })
        .then(() => {
          return new Promise((resolve) => {
            fs.stat(ebookData.epubPath,(err , stats) => { ebookSize = stats.size; return resolve(ebookSize) })
          })
        })
        .then(() => {
          Ebook.findOne({'parser': source}, {}, { sort: { 'time' : -1 } })
          .then((EbookDB) => {
            if(EbookDB != null){
              ebookSizeDB = EbookDB.size
            }else{ // primera vez que se hace un ebook de un parser
              ebookSizeDB = 0
              console.log("first time");
            }
          })
          .then(() => {
          console.log(ebookSize);
          console.log(ebookSizeDB);
            if (ebookSizeDB < ebookSize+100 && ebookSizeDB > ebookSize-100){
              console.log('same-size');
              return(true)
            }else{
              console.log('different-size');
              return uploadToS3(ebookData.epubPath, {bundleType: 'epub', name: ebookData.name})
              .then(() => {
                return uploadToS3(ebookData.mobiPath, {bundleType: 'mobi', name: ebookData.name})
              })
              .then(() => {
                console.log('db');
                const time = new Date();
                return new Ebook({parser: source, name: ebookData.name, size: ebookSize, time: time}).save()
              })
              .then(() => {
                return (true)
              })
            }
          })
          .then(() => {
            console.log('deleting-files');
             fs.unlink(ebookData.epubPath, (err) => {
               if (err) throw err;
             });
             fs.unlink(ebookData.mobiPath, (err) => {
               if (err) throw err;
             });
           })
          .then(()=> {
            console.log(schedule.source);
            console.log(schedule.healthcheckUrl);
            const healthcheckUrl = schedule.healthcheckUrl
            https.get(healthcheckUrl);
          })
        })
      },
      start: true,
      timeZone: timeZone
    })
  })
}
export default scheduleBundles;
<commit_msg>Fix timer var inside crons/index.js<commit_after>const CronJob = require('cron').CronJob
import https from 'https'
import fs from 'fs'
import Promise from 'bluebird'

import Ebook from '../models/Ebook'
import schedules from './schedules.js'
import createEbook from '../news-parser'
import uploadToS3 from '../helpers/upload-to-s3'
//Quan s'inicia el server mira la programació establerta a schedules.js i crea 1 cronjob per cada parser
let timer = 0;
function scheduleBundles(){
  schedules.forEach((schedule) => {
    timer = timer + 5;
    timer > 50  ? timer = 0 : ''
    const source = schedule.source
    const hourFreq = schedule.hourFreq
    const timeZone = schedule.timeZone
    const cronTime = `00 ${timer} ${hourFreq} * * *` // se creara el bundle cada horafreq horas, con un desfase de timer minutos
    new CronJob({
      cronTime: cronTime,
      onTick: function() {
      // variables declaration
        var ebookData
        var ebookSize
        var ebookSizeDB
      // create ebook
        createEbook([source])
        .then((data) => {
          ebookData = data
          console.log(ebookData);
        })
        .then(() => {
          return new Promise((resolve) => {
            fs.stat(ebookData.epubPath,(err , stats) => { ebookSize = stats.size; return resolve(ebookSize) })
          })
        })
        .then(() => {
          Ebook.findOne({'parser': source}, {}, { sort: { 'time' : -1 } })
          .then((EbookDB) => {
            if(EbookDB != null){
              ebookSizeDB = EbookDB.size
            }else{ // primera vez que se hace un ebook de un parser
              ebookSizeDB = 0
              console.log("first time");
            }
          })
          .then(() => {
          console.log(ebookSize);
          console.log(ebookSizeDB);
            if (ebookSizeDB < ebookSize+100 && ebookSizeDB > ebookSize-100){
              console.log('same-size');
              return(true)
            }else{
              console.log('different-size');
              return uploadToS3(ebookData.epubPath, {bundleType: 'epub', name: ebookData.name})
              .then(() => {
                return uploadToS3(ebookData.mobiPath, {bundleType: 'mobi', name: ebookData.name})
              })
              .then(() => {
                console.log('db');
                const time = new Date();
                return new Ebook({parser: source, name: ebookData.name, size: ebookSize, time: time}).save()
              })
              .then(() => {
                return (true)
              })
            }
          })
          .then(() => {
            console.log('deleting-files');
             fs.unlink(ebookData.epubPath, (err) => {
               if (err) throw err;
             });
             fs.unlink(ebookData.mobiPath, (err) => {
               if (err) throw err;
             });
           })
          .then(()=> {
            console.log(schedule.source);
            console.log(schedule.healthcheckUrl);
            const healthcheckUrl = schedule.healthcheckUrl
            https.get(healthcheckUrl);
          })
        })
      },
      start: true,
      timeZone: timeZone
    })
  })
}
export default scheduleBundles;
<|endoftext|>