<commit_before>"use strict";
/*exported Source, SourceArray*/

var Source = gpf.define("Source", {

    "private": {

        //@property {SourceArray} Source array
        _array: null,

        //@property {String} Source name
        "[_name]": [gpf.$ClassProperty()],
        _name: "",

        //@property {String} Source description
        "[_description]": [gpf.$ClassProperty(true)],
        _description: "",

        //@property {Number} Source index
        "[_index]": [gpf.$ClassProperty(true)],
        _index: -1,

        //@property {Boolean} Source is loaded
        "[_load]": [gpf.$ClassProperty()],
        _load: false,

        //@property {Boolean} Source has a test counterpart
        "[_test]": [gpf.$ClassProperty()],
        _test: false,

        //@property {Boolean} Documentation should be extracted from this source
        "[_doc]": [gpf.$ClassProperty()],
        _doc: false,

        //@property {String} Namespace
        "[_namespace]": [gpf.$ClassProperty()],
        _namespace: "",

        //@property {String[]} List of dependencies (boot is excluded)
        "[_dependsOn]": [gpf.$ClassProperty(false, "dependencies")],
        _dependsOn: [],

        //@property {String[]} List of module dependencies
        "[_dependencyOf]": [gpf.$ClassProperty()],
        _dependencyOf: [],

        /**
         * Extract from the dependencies dictionary
         *
         * @param {Object} dependencies dictionary
         */
        _processDependencies: function (dependencies) {
            var myDependencies = dependencies[this._name] || [];
            this._dependsOn = myDependencies;
            this._dependencyOf = [];
            Object.keys(dependencies).forEach(function (name) {
                var nameDependencies = dependencies[name];
                if (nameDependencies.indexOf(this._name) > -1) {
                    this._dependencyOf.push(name);
                }
            }, this);
        },

        //@property {Object[]} List of implemented items
        "[_items]": [gpf.$ClassProperty()],
        _items: [],

        /**
         * Adds an implementation item
         *
         * @param {String} type
         * @param {String} name
         */
        _addImplementationItem: function (type, name) {
            if (!this.hasOwnProperty("_items")) {
                this._items = [];
            }
            this._items.push({
                type: type,
                name: name,
                internal: name.charAt(0) === "_"
            });
        },

        /**
         * Extract implementation details
         *
         * @param {Object} source contains raw implementation
         */
        _processImplementation: function (source) {
            if (source.method) {
                this._addImplementationItem(Source.IMPLEMENTS_TYPE_METHOD, source.method);
            }
            if (source["class"]) {
                this._addImplementationItem(Source.IMPLEMENTS_TYPE_CLASS, source["class"]);
            }
            if (source.namespace) {
                this._addImplementationItem(Source.IMPLEMENTS_TYPE_NAMESPACE, source.namespace);
            }
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setLoad: function (value) {
            if (!value) {
                // Allow only if all dependencies are not loaded
                if (this._dependencyOf.some(function (name) {
                    return this._array.byName(name).getLoad();
                }, this)) {
                    return false;
                }
            }
            this._load = value;
            return true;
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setTest: function (value) {
            this._test = value;
            return true;
        },

        /**
         * Change the doc setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setDoc: function (value) {
            this._doc = value;
            return true;
        }

    },

    "public": {

        constructor: function (array, source, dependencies) {
            this._array = array;
            this._name = source.name;
            this._description = source.description;
            this._index = array.getLength();
            if (source.load !== false) {
                this._load = true;
            }
            if (source.test !== false) {
                this._test = true;
            }
            if (source.doc === true) {
                this._doc = true;
            }
            this._processImplementation(source);
            this._processDependencies(dependencies);
        },

        /**
         * Create the exported version of the source
         */
        export: function () {
            var result = {
                name: this._name
            };
            if (this._description) {
                result.description = this._description;
            }
            if (this._load) {
                if (!this._test) {
                    result.test = false;
                }
                if (this._doc) {
                    result.doc = true;
                }
            } else {
                result.load = false;
            }
            this._items.forEach(function (item) {
                if (Source.IMPLEMENTS_TYPE_METHOD === item.type) {
                    result.method = item.name;
                } else if (Source.IMPLEMENTS_TYPE_CLASS === item.type) {
                    result["class"] = item.name;
                } else if (Source.IMPLEMENTS_TYPE_NAMESPACE === item.type) {
                    result.namespace = item.name;
                }
            });
            return result;
        },

        isReadOnly: function () {
            return this._name === "boot";
        },

        testProperty: function (propertyName) {
            if (!this._load) {
                return false;
            }
            if ("test" === propertyName) {
                return this._test;
            } else if ("doc" === propertyName) {
                return this._doc;
            }
            return true;
        },

        setProperty: function (propertyName, value) {
            if ("load" === propertyName) {
                return this._setLoad(value);
            }
            if ("test" === propertyName) {
                return this._setTest(value);
            }
            if ("doc" === propertyName) {
                this._setDoc(value);
            }
        },

        hasDependencies: function () {
            return this._dependsOn.length > 0;
        },

        isReferenced: function () {
            return this._dependencyOf.length > 0;
        }

    },

    "static": {
        IMPLEMENTS_TYPE_NAMESPACE: "namespace",
        IMPLEMENTS_TYPE_METHOD: "method",
        IMPLEMENTS_TYPE_CLASS: "class"
    }

});

var SourceArray = gpf.define("SourceArray", {

    "private": {

        // @property {Object} dictionary of sources indexed by name
        _sources: []

    },

    "public": {

        constructor: function (sourcesJSON, dependenciesJSON) {
            var dependencies = JSON.parse(dependenciesJSON);
            JSON.parse(sourcesJSON).forEach(function (source) {
                this._sources.push(new Source(this, source, dependencies));
            }, this);
        },

        /**
         * Get the number of sources
         *
         * @return {Number}
         */
        getLength: function () {
            return this._sources.length;
        },

        /**
         * Enumerate all sources
         *
         * @param {Function} callback
         * @param {*} thisArg
         */
        forEach: function (callback, thisArg) {
            this._sources.forEach(callback, thisArg);
        },

        /**
         * Get the source by name
         *
         * @param {String} name
         * @return {Source}
         */
        byName: function (name) {
            var result;
            this._sources.every(function (source) {
                if (source.getName() === name) {
                    result = source;
                    return false;
                }
                return true;
            });
            return result;
        },

        /**
         * Get the source by index
         *
         * @param {Number} index
         * @return {Source}
         */
        byIndex: function (index) {
            return this._sources[index];
        },

        /**
         * Get all module names
         *
         * @return {String[]}
         */
        getNames: function () {
            return this._sources.map(function (source) {
                return source.getName();
            });
        },

        /**
         * Based on its dependencies, compute the minimum index of the module.
         *
         * @param {String} name
         * @return {Number}
         */
        getMinIndexFor: function (name) {
            var dependencies = this.byName(name).getDependencies(),
                names = this.getNames(),
                minIndex = 1; // 0 being boot
            dependencies.forEach(function (dependencyName) {
                var index = names.indexOf(dependencyName) + 1;
                if (index > minIndex) {
                    minIndex = index;
                }
            });
            return minIndex;
        },

        /**
         * Return the JSON string representing the list of sources
         *
         * @return {String}
         */
        toString: function () {
            return JSON.stringify(this._sources.map(function (source) {
                return source.export();
            }), null, 2);
        }

    }

});
<commit_msg>Contains all sources.html code<commit_after>"use strict";
/*global xhr*/
/*exported onDrag, onDragEnd, onDragOver, onDragEnter, onDragLeave, onDrop, onLoad, onSave*/

//region Source and SourceArray definitions

var Source = gpf.define("Source", {

    "private": {

        //@property {SourceArray} Source array
        _array: null,

        //@property {String} Source name
        "[_name]": [gpf.$ClassProperty()],
        _name: "",

        //@property {String} Source description
        "[_description]": [gpf.$ClassProperty(true)],
        _description: "",

        //@property {Number} Source index
        "[_index]": [gpf.$ClassProperty(true)],
        _index: -1,

        //@property {Boolean} Source is loaded
        "[_load]": [gpf.$ClassProperty()],
        _load: false,

        //@property {Boolean} Source has a test counterpart
        "[_test]": [gpf.$ClassProperty()],
        _test: false,

        //@property {Boolean} Documentation should be extracted from this source
        "[_doc]": [gpf.$ClassProperty()],
        _doc: false,

        //@property {String} Namespace
        "[_namespace]": [gpf.$ClassProperty()],
        _namespace: "",

        //@property {String[]} List of dependencies (boot is excluded)
        "[_dependsOn]": [gpf.$ClassProperty(false, "dependencies")],
        _dependsOn: [],

        //@property {String[]} List of module dependencies
        "[_dependencyOf]": [gpf.$ClassProperty()],
        _dependencyOf: [],

        /**
         * Extract from the dependencies dictionary
         *
         * @param {Object} dependencies dictionary
         */
        _processDependencies: function (dependencies) {
            var myDependencies = dependencies[this._name] || [];
            this._dependsOn = myDependencies;
            this._dependencyOf = [];
            Object.keys(dependencies).forEach(function (name) {
                var nameDependencies = dependencies[name];
                if (nameDependencies.indexOf(this._name) > -1) {
                    this._dependencyOf.push(name);
                }
            }, this);
        },

        //@property {Object[]} List of implemented items
        "[_items]": [gpf.$ClassProperty()],
        _items: [],

        /**
         * Adds an implementation item
         *
         * @param {String} type
         * @param {String} name
         */
        _addImplementationItem: function (type, name) {
            if (!this.hasOwnProperty("_items")) {
                this._items = [];
            }
            this._items.push({
                type: type,
                name: name,
                internal: name.charAt(0) === "_"
            });
        },

        /**
         * Extract implementation details
         *
         * @param {Object} source contains raw implementation
         */
        _processImplementation: function (source) {
            gpf.forEach(Source.IMPLEMENTS_MAPPING, function (type, memberName) {
                if (source[memberName]) {
                    this._addImplementationItem(type, source[memberName]);
                }
            }, this);
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setLoad: function (value) {
            if (!value) {
                // Allow only if all dependencies are not loaded
                if (this._dependencyOf.some(function (name) {
                    return this._array.byName(name).getLoad();
                }, this)) {
                    return false;
                }
            }
            this._load = value;
            return true;
        },

        /**
         * Change the load setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setTest: function (value) {
            this._test = value;
            return true;
        },

        /**
         * Change the doc setting
         *
         * @param {Boolean} value
         * @return {Boolean} Update done
         */
        _setDoc: function (value) {
            this._doc = value;
            return true;
        }

    },

    "public": {

        constructor: function (array, source, dependencies) {
            this._array = array;
            this._name = source.name;
            this._description = source.description;
            this._index = array.getLength();
            if (source.load !== false) {
                this._load = true;
            }
            if (source.test !== false) {
                this._test = true;
            }
            if (source.doc === true) {
                this._doc = true;
            }
            this._processImplementation(source);
            this._processDependencies(dependencies);
        },

        /**
         * Create the exported version of the source
         */
        export: function () {
            var result = {
                name: this._name
            };
            if (this._description) {
                result.description = this._description;
            }
            if (this._load) {
                if (!this._test) {
                    result.test = false;
                }
                if (this._doc) {
                    result.doc = true;
                }
            } else {
                result.load = false;
            }
            this._items.forEach(function (item) {
                gpf.forEach(Source.IMPLEMENTS_MAPPING, function (type, memberName) {
                    if (type === item.type) {
                        result[memberName] = item.name;
                    }
                });
            });
            return result;
        },

        isReadOnly: function () {
            return this._name === "boot";
        },

        testProperty: function (propertyName) {
            if (!this._load) {
                return false;
            }
            if ("test" === propertyName) {
                return this._test;
            } else if ("doc" === propertyName) {
                return this._doc;
            }
            return true;
        },

        setProperty: function (propertyName, value) {
            if ("load" === propertyName) {
                return this._setLoad(value);
            }
            if ("test" === propertyName) {
                return this._setTest(value);
            }
            if ("doc" === propertyName) {
                this._setDoc(value);
            }
        },

        hasDependencies: function () {
            return this._dependsOn.length > 0;
        },

        isReferenced: function () {
            return this._dependencyOf.length > 0;
        }

    },

    "static": {
        IMPLEMENTS_TYPE_NAMESPACE: "namespace",
        IMPLEMENTS_TYPE_METHOD: "method",
        IMPLEMENTS_TYPE_CLASS: "class",
        IMPLEMENTS_TYPE_INTERFACE: "interface",
        IMPLEMENTS_TYPE_MIXIN: "mixin",

        IMPLEMENTS_MAPPING: {}
    }

});

gpf.extend(Source.IMPLEMENTS_MAPPING, {
    "namespace": Source.IMPLEMENTS_TYPE_NAMESPACE,
    "method": Source.IMPLEMENTS_TYPE_METHOD,
    "class": Source.IMPLEMENTS_TYPE_CLASS,
    "interface": Source.IMPLEMENTS_TYPE_INTERFACE,
    "mixin": Source.IMPLEMENTS_TYPE_MIXIN
});

var SourceArray = gpf.define("SourceArray", {

    "private": {

        // @property {Object} dictionary of sources indexed by name
        _sources: []

    },

    "public": {

        constructor: function (sourcesJSON, dependenciesJSON) {
            var dependencies = JSON.parse(dependenciesJSON);
            JSON.parse(sourcesJSON).forEach(function (source) {
                this._sources.push(new Source(this, source, dependencies));
            }, this);
        },

        /**
         * Get the number of sources
         *
         * @return {Number}
         */
        getLength: function () {
            return this._sources.length;
        },

        /**
         * Enumerate all sources
         *
         * @param {Function} callback
         * @param {*} thisArg
         */
        forEach: function (callback, thisArg) {
            this._sources.forEach(callback, thisArg);
        },

        /**
         * Get the source by name
         *
         * @param {String} name
         * @return {Source}
         */
        byName: function (name) {
            var result;
            this._sources.every(function (source) {
                if (source.getName() === name) {
                    result = source;
                    return false;
                }
                return true;
            });
            return result;
        },

        /**
         * Get the source by index
         *
         * @param {Number} index
         * @return {Source}
         */
        byIndex: function (index) {
            return this._sources[index];
        },

        /**
         * Get all module names
         *
         * @return {String[]}
         */
        getNames: function () {
            return this._sources.map(function (source) {
                return source.getName();
            });
        },

        /**
         * Based on its dependencies, compute the minimum index of the module.
         *
         * @param {String} name
         * @return {Number}
         */
        getMinIndexFor: function (name) {
            var dependencies = this.byName(name).getDependencies(),
                names = this.getNames(),
                minIndex = 1; // 0 being boot
            dependencies.forEach(function (dependencyName) {
                var index = names.indexOf(dependencyName) + 1;
                if (index > minIndex) {
                    minIndex = index;
                }
            });
            return minIndex;
        },

        /**
         * Return the JSON string representing the list of sources
         *
         * @return {String}
         */
        toString: function () {
            return JSON.stringify(this._sources.map(function (source) {
                return source.export();
            }), null, 4);
        }

    }

});

//endregion

//region HTML page logic

var // @global {Function} Row factory
    rowFactory,
    // @global {Object} Base node receiving rows
    sourceRows,
    // @global {SourceArray} List of sources
    sources;

// Whatever the target node, get the parent TR corresponding to the source row
function upToSourceRow(target) {
    var current = target;
    while (current && current.tagName.toLowerCase() !== "tr") {
        current = current.parentNode;
    }
    return current;
}

// Regenerate the source row
function refreshSourceRow(target, source) {
    var row = upToSourceRow(target),
        newRow = rowFactory(source, source.getIndex());
    sourceRows.replaceChild(newRow, row);
}

// Regenerate all source rows
function reload() {
    sources.forEach(function (item, index) {
        sourceRows.appendChild(rowFactory(item, index));
    });
}

function onClick(event) {
    var target = event.target,
        parts,
        source;
    if ("checkbox" === target.getAttribute("type")) {
        parts = target.id.split("_"); // property_sourceIndex
        source = sources.byIndex(parseInt(parts[1], 10));
        if (source.setProperty(parts[0], target.checked)) {
            refreshSourceRow(target, source);
            document.getElementById("save").removeAttribute("disabled");
        } else {
            target.checked = !target.checked; // Restore value
        }
    } else if ("description" === target.className) {
        alert(target.innerHTML); // TODO propose an text field to edit the description
    }
}

var draggedSourceName;

function onDrag(event) {
    if (!event.target.className) {
        draggedSourceName = event.target.id;
        var minIndex = sources.getMinIndexFor(event.target.id);
        [].slice.call(sourceRows.children).forEach(function (sourceRow, index) {
            if (sourceRow === event.target) {
                sourceRow.className = "dragged";
            } else if (index < minIndex) {
                sourceRow.className = "no-drag";
            } else {
                sourceRow.className = "drag-ok";
            }
        });
    }
}

function onDragEnd(/*event*/) {
    [].slice.call(sourceRows.children).forEach(function (sourceRow) {
        sourceRow.className = "";
    });
}

function onDragOver(event) {
    if (-1 < upToSourceRow(event.target).className.indexOf("drag-ok")) {
        event.preventDefault(); // allowed
    }
}

function onDragEnter(event) {
    var row = upToSourceRow(event.target);
    if (-1 === row.className.indexOf(" over")) {
        row.className += " over";
    }
}

function onDragLeave(event) {
    var row = upToSourceRow(event.target);
    if (-1 !== row.className.indexOf(" over")) {
        row.className = row.className.split(" ")[0];
    }
}

function onDrop(event) {
    var targetSource = upToSourceRow(event.target).id;
    console.log(draggedSourceName + " -> " + targetSource);
    event.preventDefault();
}

function onLoad() {
    Promise.all([xhr("src/sources.json").get(), xhr("build/dependencies.json").get()])
        .then(function (responseTexts) {
            var tplRow = document.getElementById("tpl_row");
            rowFactory = tplRow.buildFactory();
            sourceRows = document.getElementById("rows");
            sources = new SourceArray(responseTexts[0], responseTexts[1]);
            reload();
            document.addEventListener("click", onClick);
        }, function (reason) {
            alert("A problem occurred while loading src/sources.json and build/dependencies.json: " + reason);
        });
}

function onSave() {
    xhr("/file/src/sources.json").put(sources.toString())
        .then(function () {
            document.getElementById("save").setAttribute("disabled", true);
        }, function (reason) {
            alert(reason);
        });
}

//endregion
<|endoftext|>


<commit_before>// Generated on 2014-01-03 using generator-webapp 0.4.6
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

    // Time how long tasks take. Can help when optimizing build times
    require('time-grunt')(grunt);

    var modRewrite = require('connect-modrewrite');

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist'
        },

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            jstest: {
                files: ['test/spec/{,*/}*.js'],
                tasks: ['test:watch']
            },
            gruntfile: {
                files: ['Gruntfile.js']
            },
            compass: {
                files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server', 'autoprefixer']
            },
            styles: {
                files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
                tasks: ['newer:copy:styles', 'autoprefixer']
            },
            livereload: {
                options: {
                    livereload: '<%= connect.options.livereload %>'
                },
                files: [
                    '<%= yeoman.app %>/{,*/}*.html',
                    '.tmp/scripts/main.js',
                    '.tmp/styles/{,*/}*.css',
                    '<%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
                ]
            },
            packagejson: {
                files: ['package.json'],
                tasks: ['browserify']
            }
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // Change this to '0.0.0.0' to access the server from outside
                hostname: '0.0.0.0',
                middleware: function(connect, options, middlewares) {
                    // Rewrite everything that does not contain a '.' to
                    // support pushState
                    middlewares.unshift(modRewrite(['^[^\\.]*$ /index.html [L]']));
                    return middlewares;
                }
            },
            livereload: {
                options: {
                    open: true,
                    base: [
                        '.tmp',
                        '<%= yeoman.app %>',
                        '.',
                        'api/app'
                    ]
                }
            },
            test: {
                options: {
                    port: 9001,
                    base: [
                        '.tmp',
                        'test',
                        '<%= yeoman.app %>',
                        'api/app'
                    ]
                }
            },
            dist: {
                options: {
                    open: true,
                    base: [
                        '<%= yeoman.dist %>',
                        'api/app'
                    ],
                    livereload: false
                }
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%= yeoman.dist %>/*',
                        '!<%= yeoman.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%= yeoman.app %>/scripts/**/*.js',
                '!<%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },

        // Mocha testing framework configuration options
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
                }
            }
        },

        // Compiles Sass to CSS and generates necessary files if requested
        compass: {
            options: {
                sassDir: '<%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                generatedImagesDir: '.tmp/images/generated',
                imagesDir: '<%= yeoman.app %>/images',
                javascriptsDir: '<%= yeoman.app %>/scripts',
                fontsDir: '<%= yeoman.app %>/styles/fonts',
                importPath: '<%= yeoman.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                httpFontsPath: '/styles/fonts',
                sourcemap: true,
                bundleExec: true,
                relativeAssets: false,
                assetCacheBuster: false
            },
            dist: {
                options: {
                    generatedImagesDir: '<%= yeoman.dist %>/images/generated'
                }
            },
            server: {
                options: {
                    debugInfo: false
                }
            }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: {
                // Commented out to use more extensive autoprefixer defaults
                // browsers: ['last 1 version']
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/styles/',
                    src: '{,*/}*.css',
                    dest: '.tmp/styles/'
                }]
            }
        },

        browserify: {
            all: {
                files: {
                    '.tmp/scripts/main.js': '<%= yeoman.app %>/scripts/main.js'
                },
                options: {
                    browserifyOptions: {
                        debug: true
                    },
                    watch: true
                }
            }
        },

        // Renames files for browser caching purposes
        rev: {
            dist: {
                files: {
                    src: [
                        '<%= yeoman.dist %>/scripts/{,*/}*.js',
                        '<%= yeoman.dist %>/styles/{,*/}*.css',
                        '!<%= yeoman.dist %>/styles/slate.min.css',
                        '<%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}',
//                        '<%= yeoman.dist %>/styles/fonts/{,*/}*.*'
                    ]
                }
            }
        },

        // Reads HTML for usemin blocks to enable smart builds that automatically
        // concat, minify and revision files. Creates configurations in memory so
        // additional tasks can operate on them
        useminPrepare: {
            options: {
                dest: '<%= yeoman.dist %>'
            },
            html: '.tmp/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: {
                assetsDirs: ['<%= yeoman.dist %>']
            },
            html: ['<%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.app %>/images',
                    src: '{,*/}*.{gif,jpeg,jpg,png}',
                    dest: '<%= yeoman.dist %>/images'
                }, {
                  expand: true,
                  cwd: '<%= yeoman.app %>/bower_components/select2',
                  src: '*.{gif,png}',
                  dest: '<%= yeoman.dist %>/styles'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%= yeoman.dist %>/images'
                }]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.dist %>',
                    src: '{,*/}*.html',
                    dest: '<%= yeoman.dist %>'
                }]
            }
        },

        // By default, your `index.html`'s <!-- Usemin block --> will take care of
        // minification. These next options are pre-configured if you do not wish
        // to use the Usemin blocks.
        cssmin: {
            dist: {
                files: {
                    '<%= yeoman.dist %>/styles/exports.css': [
                        '.tmp/styles/exports.css'
                    ]
                }
            }
        },
        // uglify: {
        //     dist: {
        //         files: {
        //             '<%= yeoman.dist %>/scripts/scripts.js': [
        //                 '<%= yeoman.dist %>/scripts/scripts.js'
        //             ]
        //         }
        //     }
        // },
        // concat: {
        //     dist: {}
        // },

        // Copies remaining files to places other tasks can use
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= yeoman.app %>',
                    dest: '<%= yeoman.dist %>',
                    src: [
                        '*.{config,php}',
                        '*.{ico,png,txt}',
                        '.htaccess',
                        'images/{,*/}*.webp',
                        '{,*/}*.html',
                        'scripts/disqus-count.js',
                        'styles/slate.min.css',
                        'styles/*.{gif,png}',
                        'styles/fonts/{,*/}*.*',
                        'vendor/knplabs/knp-snappy/src/**/*.php',
                        'bower_components/font-awesome/fonts/*.*'
                    ]
                }, {
                  dest: '<%= yeoman.dist %>/ZeroClipboard.swf',
                  src: 'node_modules/zeroclipboard/dist/ZeroClipboard.swf'
                }, {
                    expand: true,
                    cwd: '.tmp',
                    dest: '<%= yeoman.dist %>',
                    src: 'index.html'
                }]
            },
            styles: {
                expand: true,
                dot: true,
                cwd: '<%= yeoman.app %>/styles',
                dest: '.tmp/styles/',
                src: '{,*/}*.css'
            },
            tmp: {
                expand: true,
                cwd: '<%= yeoman.app %>',
                dest: '.tmp',
                src: [
                    'bower_components/qtip2/jquery.qtip.css',
                    'bower_components/select2/select2.css',
                    'bower_components/animate.css/animate.min.css',
                    'index.html'
                ]
            }
        },

        // Run some tasks in parallel to speed up build process
        concurrent: {
            server: [
                'compass:server',
                'copy:styles'
            ],
            test: [
                'copy:styles'
            ],
            dist: [
                'compass',
                'copy:styles',
                'imagemin',
                'svgmin'
            ]
        },
        rsync: {
            options: {
                args: ['-cruv']
            },
            'nusmods.com': {
                options: {
                    src: '<%= yeoman.dist %>/*',
                    dest: '~/nusmods.com'
                }
            }
        }
    });

    grunt.registerTask('serve', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'concurrent:server',
            'autoprefixer',
            'browserify',
            'connect:livereload',
            'watch'
        ]);
    });

    grunt.registerTask('server', function () {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run(['serve']);
    });

    grunt.registerTask('test', function(target) {
        if (target !== 'watch') {
            grunt.task.run([
                'clean:server',
                'concurrent:test',
                'autoprefixer'
            ]);
        }

        grunt.task.run([
            'connect:test',
            'mocha'
        ]);
    });

    grunt.registerTask('build', [
        'clean:dist',
        'copy:tmp',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'browserify',
        'concat',
        'cssmin',
        'uglify',
        'copy:dist',
        'rev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'newer:jshint',
        // 'test',
        'build'
    ]);
};
<commit_msg>Included FB SDK in grunt<commit_after>// Generated on 2014-01-03 using generator-webapp 0.4.6
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

    // Time how long tasks take. Can help when optimizing build times
    require('time-grunt')(grunt);

    var modRewrite = require('connect-modrewrite');

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist'
        },

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            jstest: {
                files: ['test/spec/{,*/}*.js'],
                tasks: ['test:watch']
            },
            gruntfile: {
                files: ['Gruntfile.js']
            },
            compass: {
                files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server', 'autoprefixer']
            },
            styles: {
                files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
                tasks: ['newer:copy:styles', 'autoprefixer']
            },
            livereload: {
                options: {
                    livereload: '<%= connect.options.livereload %>'
                },
                files: [
                    '<%= yeoman.app %>/{,*/}*.html',
                    '.tmp/scripts/main.js',
                    '.tmp/styles/{,*/}*.css',
                    '<%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
                ]
            },
            packagejson: {
                files: ['package.json'],
                tasks: ['browserify']
            }
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // Change this to '0.0.0.0' to access the server from outside
                hostname: '0.0.0.0',
                middleware: function(connect, options, middlewares) {
                    // Rewrite everything that does not contain a '.' to
                    // support pushState
                    middlewares.unshift(modRewrite(['^[^\\.]*$ /index.html [L]']));
                    return middlewares;
                }
            },
            livereload: {
                options: {
                    open: true,
                    base: [
                        '.tmp',
                        '<%= yeoman.app %>',
                        '.',
                        'api/app'
                    ]
                }
            },
            test: {
                options: {
                    port: 9001,
                    base: [
                        '.tmp',
                        'test',
                        '<%= yeoman.app %>',
                        'api/app'
                    ]
                }
            },
            dist: {
                options: {
                    open: true,
                    base: [
                        '<%= yeoman.dist %>',
                        'api/app'
                    ],
                    livereload: false
                }
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp',
                        '<%= yeoman.dist %>/*',
                        '!<%= yeoman.dist %>/.git*'
                    ]
                }]
            },
            server: '.tmp'
        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%= yeoman.app %>/scripts/**/*.js',
                '!<%= yeoman.app %>/scripts/vendor/*',
                'test/spec/{,*/}*.js'
            ]
        },

        // Mocha testing framework configuration options
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
                }
            }
        },

        // Compiles Sass to CSS and generates necessary files if requested
        compass: {
            options: {
                sassDir: '<%= yeoman.app %>/styles',
                cssDir: '.tmp/styles',
                generatedImagesDir: '.tmp/images/generated',
                imagesDir: '<%= yeoman.app %>/images',
                javascriptsDir: '<%= yeoman.app %>/scripts',
                fontsDir: '<%= yeoman.app %>/styles/fonts',
                importPath: '<%= yeoman.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                httpFontsPath: '/styles/fonts',
                sourcemap: true,
                bundleExec: true,
                relativeAssets: false,
                assetCacheBuster: false
            },
            dist: {
                options: {
                    generatedImagesDir: '<%= yeoman.dist %>/images/generated'
                }
            },
            server: {
                options: {
                    debugInfo: false
                }
            }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: {
                // Commented out to use more extensive autoprefixer defaults
                // browsers: ['last 1 version']
            },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/styles/',
                    src: '{,*/}*.css',
                    dest: '.tmp/styles/'
                }]
            }
        },

        browserify: {
            all: {
                files: {
                    '.tmp/scripts/main.js': '<%= yeoman.app %>/scripts/main.js'
                },
                options: {
                    browserifyOptions: {
                        debug: true
                    },
                    watch: true
                }
            }
        },

        // Renames files for browser caching purposes
        rev: {
            dist: {
                files: {
                    src: [
                        '<%= yeoman.dist %>/scripts/{,*/}*.js',
                        '<%= yeoman.dist %>/styles/{,*/}*.css',
                        '!<%= yeoman.dist %>/styles/slate.min.css',
                        '<%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}',
//                        '<%= yeoman.dist %>/styles/fonts/{,*/}*.*'
                    ]
                }
            }
        },

        // Reads HTML for usemin blocks to enable smart builds that automatically
        // concat, minify and revision files. Creates configurations in memory so
        // additional tasks can operate on them
        useminPrepare: {
            options: {
                dest: '<%= yeoman.dist %>'
            },
            html: '.tmp/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: {
                assetsDirs: ['<%= yeoman.dist %>']
            },
            html: ['<%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.app %>/images',
                    src: '{,*/}*.{gif,jpeg,jpg,png}',
                    dest: '<%= yeoman.dist %>/images'
                }, {
                  expand: true,
                  cwd: '<%= yeoman.app %>/bower_components/select2',
                  src: '*.{gif,png}',
                  dest: '<%= yeoman.dist %>/styles'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.app %>/images',
                    src: '{,*/}*.svg',
                    dest: '<%= yeoman.dist %>/images'
                }]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%= yeoman.dist %>',
                    src: '{,*/}*.html',
                    dest: '<%= yeoman.dist %>'
                }]
            }
        },

        // By default, your `index.html`'s <!-- Usemin block --> will take care of
        // minification. These next options are pre-configured if you do not wish
        // to use the Usemin blocks.
        cssmin: {
            dist: {
                files: {
                    '<%= yeoman.dist %>/styles/exports.css': [
                        '.tmp/styles/exports.css'
                    ]
                }
            }
        },
        // uglify: {
        //     dist: {
        //         files: {
        //             '<%= yeoman.dist %>/scripts/scripts.js': [
        //                 '<%= yeoman.dist %>/scripts/scripts.js'
        //             ]
        //         }
        //     }
        // },
        // concat: {
        //     dist: {}
        // },

        // Copies remaining files to places other tasks can use
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%= yeoman.app %>',
                    dest: '<%= yeoman.dist %>',
                    src: [
                        '*.{config,php}',
                        '*.{ico,png,txt}',
                        '.htaccess',
                        'images/{,*/}*.webp',
                        '{,*/}*.html',
                        'scripts/disqus-count.js',
                        'styles/slate.min.css',
                        'styles/*.{gif,png}',
                        'styles/fonts/{,*/}*.*',
                        'vendor/knplabs/knp-snappy/src/**/*.php',
                        'vendor/facebook/php-sdk-v4/autoload.php',
                        'vendor/facebook/php-sdk-v4/src/Facebook/**/*.php',
                        'bower_components/font-awesome/fonts/*.*'
                    ]
                }, {
                  dest: '<%= yeoman.dist %>/ZeroClipboard.swf',
                  src: 'node_modules/zeroclipboard/dist/ZeroClipboard.swf'
                }, {
                    expand: true,
                    cwd: '.tmp',
                    dest: '<%= yeoman.dist %>',
                    src: 'index.html'
                }]
            },
            styles: {
                expand: true,
                dot: true,
                cwd: '<%= yeoman.app %>/styles',
                dest: '.tmp/styles/',
                src: '{,*/}*.css'
            },
            tmp: {
                expand: true,
                cwd: '<%= yeoman.app %>',
                dest: '.tmp',
                src: [
                    'bower_components/qtip2/jquery.qtip.css',
                    'bower_components/select2/select2.css',
                    'bower_components/animate.css/animate.min.css',
                    'index.html'
                ]
            }
        },

        // Run some tasks in parallel to speed up build process
        concurrent: {
            server: [
                'compass:server',
                'copy:styles'
            ],
            test: [
                'copy:styles'
            ],
            dist: [
                'compass',
                'copy:styles',
                'imagemin',
                'svgmin'
            ]
        },
        rsync: {
            options: {
                args: ['-cruv']
            },
            'nusmods.com': {
                options: {
                    src: '<%= yeoman.dist %>/*',
                    dest: '~/nusmods.com'
                }
            }
        }
    });

    grunt.registerTask('serve', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'concurrent:server',
            'autoprefixer',
            'browserify',
            'connect:livereload',
            'watch'
        ]);
    });

    grunt.registerTask('server', function () {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run(['serve']);
    });

    grunt.registerTask('test', function(target) {
        if (target !== 'watch') {
            grunt.task.run([
                'clean:server',
                'concurrent:test',
                'autoprefixer'
            ]);
        }

        grunt.task.run([
            'connect:test',
            'mocha'
        ]);
    });

    grunt.registerTask('build', [
        'clean:dist',
        'copy:tmp',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'browserify',
        'concat',
        'cssmin',
        'uglify',
        'copy:dist',
        'rev',
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'newer:jshint',
        // 'test',
        'build'
    ]);
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    run: {
      jasmine_node_with_coverage: {
        cmd: 'node_modules/.bin/grunt',
        args: ['jasmine_node_with_coverage']
      }
    },
    clean: {
      all: 'coverage'
    },
    jshint: {
      all: [
        'Gruntfile.js',
        'test/**/*.js',
        'lib/**/*.js',
        'example/**/*.js'
      ],
      options: {
        jshintrc: '.jshintrc'
      }
    },
    jasmine_node_no_coverage: {
      coverage: false,
      options: {
        matchall: true,
        specFolders: ['test/spec/'],
        isVerbose: true
      }
    },
    jasmine_node_with_coverage: {
      coverage: {
        excludes: ['test/**', 'coverage/**', 'Gruntfile.js', 'example/**']
      },
      options: {
        matchall: true,
        specFolders: ['test/spec/'],
        isVerbose: true
      }
    },
    coveralls: {
      all: {
        src: './coverage/lcov.info'
      }
    },
    watch: {
      js: {
        files: ['**/*.js', '!node_modules/**/*.js', '!coverage/**/*.js'],
        tasks: ['default'],
        options: {
          spawn: true // Since livereload seems not to be working we spawn to require the latest index.js
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-run');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-jasmine-node-coverage');
  grunt.loadNpmTasks('grunt-coveralls');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('jasmine_node_no_coverage', 'Just unit testing', function () {
    grunt.config.set('jasmine_node', grunt.config.get('jasmine_node_no_coverage'));
    grunt.task.run('jasmine_node');
  });

  grunt.registerTask('jasmine_node_with_coverage', 'Unit testing with coverage report', function () {
    grunt.config.set('jasmine_node', grunt.config.get('jasmine_node_with_coverage'));
    grunt.task.run('jasmine_node');
  });

  // 'jasmine-node' needs to run in a separate task because the coverage report is created when grunt exits.
  // When we use watch grunt keeps running and no report would be generated if 'jasmine-node' would be executed directly.
  grunt.registerTask('test', ['clean', 'jshint', 'run:jasmine_node_with_coverage', 'watch']);
  grunt.registerTask('test_no_coverage', ['clean', 'jshint', 'jasmine_node_no_coverage', 'watch']);

  // 'jasmine-node' needs to run in a separate task the second time it is executed so that es-sequence freshly required.
  // The unit tests assume that es-sequence is not initialized in the beginning.
  grunt.registerTask('ci', ['clean', 'jshint', 'jasmine_node_no_coverage', 'run:jasmine_node_with_coverage', 'coveralls']);

  grunt.registerTask('default', ['test']);
};
<commit_msg>Second test run on Travis CI without console output<commit_after>'use strict';

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    run: {
      jasmine_node_with_coverage: {
        cmd: 'node_modules/.bin/grunt',
        args: ['jasmine_node_with_coverage']
      },
      jasmine_node_with_coverage_quiet: {
        cmd: 'node_modules/.bin/grunt',
        args: ['jasmine_node_with_coverage'],
        options: {
          quiet: true
        }
      }
    },
    clean: {
      all: 'coverage'
    },
    jshint: {
      all: [
        'Gruntfile.js',
        'test/**/*.js',
        'lib/**/*.js',
        'example/**/*.js'
      ],
      options: {
        jshintrc: '.jshintrc'
      }
    },
    jasmine_node_no_coverage: {
      coverage: false,
      options: {
        matchall: true,
        specFolders: ['test/spec/'],
        isVerbose: true
      }
    },
    jasmine_node_with_coverage: {
      coverage: {
        excludes: ['test/**', 'coverage/**', 'Gruntfile.js', 'example/**']
      },
      options: {
        matchall: true,
        specFolders: ['test/spec/'],
        isVerbose: true
      }
    },
    coveralls: {
      all: {
        src: './coverage/lcov.info'
      }
    },
    watch: {
      js: {
        files: ['**/*.js', '!node_modules/**/*.js', '!coverage/**/*.js'],
        tasks: ['default'],
        options: {
          spawn: true // Since livereload seems not to be working we spawn to require the latest index.js
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-run');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-jasmine-node-coverage');
  grunt.loadNpmTasks('grunt-coveralls');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('jasmine_node_no_coverage', 'Just unit testing', function () {
    grunt.config.set('jasmine_node', grunt.config.get('jasmine_node_no_coverage'));
    grunt.task.run('jasmine_node');
  });

  grunt.registerTask('jasmine_node_with_coverage', 'Unit testing with coverage report', function () {
    grunt.config.set('jasmine_node', grunt.config.get('jasmine_node_with_coverage'));
    grunt.task.run('jasmine_node');
  });

  // 'jasmine-node' needs to run in a separate task because the coverage report is created when grunt exits.
  // When we use watch grunt keeps running and no report would be generated if 'jasmine-node' would be executed directly.
  grunt.registerTask('test', ['clean', 'jshint', 'run:jasmine_node_with_coverage', 'watch']);
  grunt.registerTask('test_no_coverage', ['clean', 'jshint', 'jasmine_node_no_coverage', 'watch']);

  // 'jasmine-node' needs to run in a separate task the second time it is executed so that es-sequence freshly required.
  // The unit tests assume that es-sequence is not initialized in the beginning.
  grunt.registerTask('ci', ['clean', 'jshint', 'jasmine_node_no_coverage', 'run:jasmine_node_with_coverage_quiet', 'coveralls']);

  grunt.registerTask('default', ['test']);
};
<|endoftext|>


<commit_before>const express = require('express')
const logger = require('morgan')
const session = require('express-session')
const bodyParser = require('body-parser')
const path = require('path')
const Twitter = require('twitter')
const Grant = require('grant-express')
const url = require('url')
const RedisStore = require('connect-redis')(session)
const { REDIS_URL, SESSION_TTL, SESSION_SECRET, CONSUMER_KEY, CONSUMER_SECRET, PROTOCOL, HOST, PORT } = process.env

const createClient = (access_token_key, access_token_secret) => {
  return new Twitter({
    consumer_key: CONSUMER_KEY,
    consumer_secret: CONSUMER_SECRET,
    access_token_key,
    access_token_secret
  })
}

const post = (client, url, params) => {
  return new Promise((resolve, reject) => {
    client.post(url, params, (error, result) => {
      if (error) reject(error)
      else resolve(result)
    })
  })
}

const grant = new Grant({
  server: {
    protocol: PROTOCOL,
    host: HOST
  },
  twitter: {
    key: CONSUMER_KEY,
    secret: CONSUMER_SECRET,
    callback: '/handle_twitter_callback'
  }
})

var app = express()
app.use(express.static(path.join(__dirname, '../build')))
app.use(logger('dev'))
app.use(session({
  secret: SESSION_SECRET,
  store: new RedisStore({
    ttl: parseInt(SESSION_TTL),
    url: REDIS_URL
  })
}))
app.use(grant)
app.use(bodyParser.json())
app.use((req, res, next) => {
  if(req.header('x-forwarded-proto') !== PROTOCOL) {
    res.redirect(`${PROTOCOL}://${req.header('host')}${req.url}`)
  } else {
    next()
  }
})

app.post('/post_tweets', (req, res) => {
  if (!req.session.auth) {
    res.sendStatus(403)
    return
  }

  const { access_token, access_secret } = req.session.auth
  const client = createClient(access_token, access_secret)

  const tweets = req.body.tweets;
  if (tweets.length === 0) {
    res.sendStatus(400)
    return
  } else {
    try {
      let ids = []
      const promise = tweets.reduce((promise, nextTweet) => {
        return promise.then((tweet) => {
          return post(client, 'statuses/update', {
            status: nextTweet,
            in_reply_to_status_id: tweet ? tweet.id_str : undefined
          })
          .then((newTweet) => {
            ids.push(newTweet.id_str)
            return newTweet
          })
        })
      }, Promise.resolve())

      promise
        .then(() => {
          res.json({ tweetId: ids[0] })
        })
        .catch((e) => {
          console.log('inner error')
          console.log(e)
          return Promise.all(ids.map((nextId) => {
            return post(client, `statuses/destroy/${nextId}`, { trim_user: true })
          }))
          .then(() => {
            res.sendStatus(500)
          })
          .catch((e2) => {
            console.log('innermost error')
            console.log(e2.stack)
            res.sendStatus(500)
          })
        })
    } catch (e) {
      console.log('outer error')
      console.log(e)
      throw e
    }
  }
})

const callbackHtml = (user) => {
  return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <script>
      window.opener.postMessage(${JSON.stringify(user)}, '${PROTOCOL}://${HOST}')
      window.close()
    </script>
  </body>
</html>
`
}

app.get('/handle_twitter_callback', function (req, res) {
  const client = createClient(req.query.access_token, req.query.access_secret)

  if (req.query.error) {
    res.send(callbackHtml(null))
    return
  }

  client.get('users/show', { screen_name: req.query.raw.screen_name }, (error, user) => {
    if (error) {
      res.send(callbackHtml(null))
    } else {
      req.session.auth = req.query
      req.session.user = {
        id: user.id_str,
        username: user.screen_name,
        image: user.profile_image_url,
        color : '#' + user.profile_link_color
      }
      res.send(callbackHtml(req.session.user))
    }
  })
})

app.get('/', (req, res) => {
  res.send(
`<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="user-scalable=no,initial-scale=1,minimum-scale=1,maximum-scale=1,width=device-width" />
    <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <script src="main.js"></script>
    <script>
      var app = Elm.Main.fullscreen({
        user: ${JSON.stringify(req.session.user || null)}
      })

      window.addEventListener('message', function(e) {
        app.ports.loginCompleteRaw.send(e.data)
      })

      app.ports.loginStart.subscribe(function() {
        window.open('/connect/twitter')
      })
    </script>
  </body>
</html>
`
  )
})

app.use((error, req, res, next) => {
  if (error) {
    console.log('middleware error')
    console.log(error)
  }
  next(error)
});

app.listen(PORT, function() {
  console.log('Express server listening on port ' + PORT)
})
<commit_msg>save some bytes in session<commit_after>const express = require('express')
const logger = require('morgan')
const session = require('express-session')
const bodyParser = require('body-parser')
const path = require('path')
const Twitter = require('twitter')
const Grant = require('grant-express')
const url = require('url')
const RedisStore = require('connect-redis')(session)
const { REDIS_URL, SESSION_TTL, SESSION_SECRET, CONSUMER_KEY, CONSUMER_SECRET, PROTOCOL, HOST, PORT } = process.env

const createClient = (access_token_key, access_token_secret) => {
  return new Twitter({
    consumer_key: CONSUMER_KEY,
    consumer_secret: CONSUMER_SECRET,
    access_token_key,
    access_token_secret
  })
}

const post = (client, url, params) => {
  return new Promise((resolve, reject) => {
    client.post(url, params, (error, result) => {
      if (error) reject(error)
      else resolve(result)
    })
  })
}

const grant = new Grant({
  server: {
    protocol: PROTOCOL,
    host: HOST
  },
  twitter: {
    key: CONSUMER_KEY,
    secret: CONSUMER_SECRET,
    callback: '/handle_twitter_callback'
  }
})

var app = express()
app.use(express.static(path.join(__dirname, '../build')))
app.use(logger('dev'))
app.use(session({
  secret: SESSION_SECRET,
  store: new RedisStore({
    ttl: parseInt(SESSION_TTL),
    url: REDIS_URL
  })
}))
app.use(grant)
app.use(bodyParser.json())
app.use((req, res, next) => {
  if(req.header('x-forwarded-proto') !== PROTOCOL) {
    res.redirect(`${PROTOCOL}://${req.header('host')}${req.url}`)
  } else {
    next()
  }
})

app.post('/post_tweets', (req, res) => {
  if (!req.session.auth) {
    res.sendStatus(403)
    return
  }

  const { token, secret } = req.session.auth
  const client = createClient(token, secret)

  const tweets = req.body.tweets;
  if (tweets.length === 0) {
    res.sendStatus(400)
    return
  } else {
    try {
      let ids = []
      const promise = tweets.reduce((promise, nextTweet) => {
        return promise.then((tweet) => {
          return post(client, 'statuses/update', {
            status: nextTweet,
            in_reply_to_status_id: tweet ? tweet.id_str : undefined
          })
          .then((newTweet) => {
            ids.push(newTweet.id_str)
            return newTweet
          })
        })
      }, Promise.resolve())

      promise
        .then(() => {
          res.json({ tweetId: ids[0] })
        })
        .catch((e) => {
          console.log('inner error')
          console.log(e)
          return Promise.all(ids.map((nextId) => {
            return post(client, `statuses/destroy/${nextId}`, { trim_user: true })
          }))
          .then(() => {
            res.sendStatus(500)
          })
          .catch((e2) => {
            console.log('innermost error')
            console.log(e2.stack)
            res.sendStatus(500)
          })
        })
    } catch (e) {
      console.log('outer error')
      console.log(e)
      throw e
    }
  }
})

const callbackHtml = (user) => {
  return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <script>
      window.opener.postMessage(${JSON.stringify(user)}, '${PROTOCOL}://${HOST}')
      window.close()
    </script>
  </body>
</html>
`
}

app.get('/handle_twitter_callback', function (req, res) {
  const client = createClient(req.query.access_token, req.query.access_secret)

  if (req.query.error) {
    res.send(callbackHtml(null))
    return
  }

  client.get('users/show', { screen_name: req.query.raw.screen_name }, (error, user) => {
    if (error) {
      res.send(callbackHtml(null))
    } else {
      req.session.auth = {
        token: req.query.access_token,
        secret: req.query.access_secret
      }

      req.session.user = {
        id: user.id_str,
        username: user.screen_name,
        image: user.profile_image_url,
        color : '#' + user.profile_link_color
      }
      res.send(callbackHtml(req.session.user))
    }
  })
})

app.get('/', (req, res) => {
  res.send(
`<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="user-scalable=no,initial-scale=1,minimum-scale=1,maximum-scale=1,width=device-width" />
    <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <script src="main.js"></script>
    <script>
      var app = Elm.Main.fullscreen({
        user: ${JSON.stringify(req.session.user || null)}
      })

      window.addEventListener('message', function(e) {
        app.ports.loginCompleteRaw.send(e.data)
      })

      app.ports.loginStart.subscribe(function() {
        window.open('/connect/twitter')
      })
    </script>
  </body>
</html>
`
  )
})

app.use((error, req, res, next) => {
  if (error) {
    console.log('middleware error')
    console.log(error)
  }
  next(error)
});

app.listen(PORT, function() {
  console.log('Express server listening on port ' + PORT)
})
<|endoftext|>


<commit_before>function GetFile(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = () => {
        if (xhr.readyState === xhr.DONE) {
            if (xhr.status === 200) {
                callback(xhr.responseText);
            }
        }
    };

    xhr.send(null);
}

function AppendScript(url, callback) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.onload = callback;
    script.src = url;
    document.body.appendChild(script);
}

var onLoadCount = 0;
var totalOnLoad = 0;
function LoadScripts(scriptUrls, callback, index) {
    if(index === undefined) {
        index = 0;
        totalOnLoad += scriptUrls.length;
    }

    if(index >= scriptUrls.length) {
        if(onLoadCount >= totalOnLoad) {
            onLoadCount = 0;
            totalOnLoad = 0;
            callback();
        }
        return;
    }

    AppendScript(scriptUrls[index], () => {
        onLoadCount++;
        LoadScripts(scriptUrls, callback, index + 1);
    });
}

function LoadCSS(cssUrl) {
    var css = document.createElement("link");
    css.rel = "stylesheet";
    css.href = cssUrl;
    document.body.appendChild(css);
}

var loading = false;
var loaded = false;
var callbacks = [];
function AddDependencies(scriptFolder, callback) {
    if(loaded) {
        callback();
        return;
    }

    callbacks.push(callback);
    if(loading)
        return;

    loading = true;
    function onComplete() {
        loaded = true;
        callbacks.forEach(cb => cb());
        callbacks = [];
    }

    LoadScripts(["https://cdn.jsdelivr.net/gh/Microsoft/TypeScript@master/lib/typescriptServices.js"], onComplete);
    // LoadScripts(["https://unpkg.com/j-templates/jTemplates.js"], callback);
    LoadScripts([
        scriptFolder + "codemirror.js",
        scriptFolder + "javascript.js",
        // scriptFolder + "show-hint.js",
        // scriptFolder + "javascript-hint.js"
    ], onComplete);
    // LoadScript(scriptFolder + "javascript.js", callback);
    LoadCSS(scriptFolder + "styles/codemirror.css");
    LoadCSS(scriptFolder + "styles/styles.css");
    LoadCSS(scriptFolder + "styles/show-hint.css");
}

function HandleError(id, message, source, lineNo, colNo, error) {
    console.debug(arguments);
    var parentDoc = window.parent.document;
    var errorSpan = parentDoc.getElementById(id + "_sample");
    errorSpan.innerHTML = message;
    return false;
}

function ExecuteTs(id, code) {
    var container = document.getElementById(id + "_output");
    var errorSpan = document.getElementById(id + "_error");
    errorSpan.innerHTML = "";

    var iframe = container.querySelector("iframe");
    if(iframe)
        container.removeChild(iframe);

    iframe = document.createElement("iframe");
    iframe.className = "output";
    iframe.srcDoc = '<!DOCTYPE html>';
    container.appendChild(iframe);
    var jTempScript = iframe.contentDocument.createElement("script");
    jTempScript.type = "text/javascript";
    jTempScript.src = "https://unpkg.com/j-templates/jTemplates.js";
    jTempScript.onload = () => {
        var js = ts.transpile(code, { target: "es6" });
        js = js.replace(/^import.*$/gm, "");
        js = HandleError.toString() + '; var id="' + id + '";  var errorHandler = HandleError.bind(null, id); window.onerror = errorHandler; ' + js;
        var script = iframe.contentDocument.createElement("script");
        script.type = "text/javascript";
        script.innerHTML = js;
        iframe.contentDocument.head.appendChild(script);
    };
    iframe.contentDocument.head.appendChild(jTempScript);
}

var changeTimeout = null;
function CreateCodeMirror(id, initValue) {
    var container = document.getElementById(id + "_code");
    var cm = CodeMirror(container, {
        value: initValue,
        matchBrackets: true,
        lineNumbers: true,
        extraKeys: { "Ctrl-Space": "autocomplete" },
        mode: {name: "text/typescript", globalVars: false}
    });
    cm.on("change", () => {
        clearTimeout(changeTimeout);
        changeTimeout = setTimeout(() => {
            var div = document.getElementById(id + "_output");
            ExecuteTs(container.id, div, cm.getDoc().getValue());
        }, 4000);
    });
}

function CreateSample(sample) {
    var container = document.getElementById(sample);
    container.innerHTML = "Loading...";
    var curScriptUrl = document.querySelector("[src$='docHelpers.js'").src;
    var curScriptFolder = curScriptUrl.replace(/docHelpers\.js$/, "");
    AddDependencies(curScriptFolder, () => {
        var samplesFolder = curScriptFolder + "../samples/";
        var sampleUrl = samplesFolder + sample + ".ts";

        GetFile(sampleUrl, (text) => {
            container.innerHTML = "";

            var div = document.createElement("div");
            div.id = sample + "_code";
            div.className = "code";

            var h2 = document.createElement("h2");
            h2.innerText = "Code";
            var span = document.createElement("span");
            span.id = sample + "_error";
            span.className = "error";
            h2.appendChild(span);
            div.appendChild(h2);
            CreateCodeMirror(id, div, text);
            container.appendChild(div);

            div = document.createElement("div");
            div.id = sample + "_output";
            div.className = "output";

            h2 = document.createElement("h2");
            h2.innerText = "Output";
            div.appendChild(h2);
            container.appendChild(div);
            ExecuteTs(sample, text);
        });
    });
}
<commit_msg>bug fix<commit_after>function GetFile(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = () => {
        if (xhr.readyState === xhr.DONE) {
            if (xhr.status === 200) {
                callback(xhr.responseText);
            }
        }
    };

    xhr.send(null);
}

function AppendScript(url, callback) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.onload = callback;
    script.src = url;
    document.body.appendChild(script);
}

var onLoadCount = 0;
var totalOnLoad = 0;
function LoadScripts(scriptUrls, callback, index) {
    if(index === undefined) {
        index = 0;
        totalOnLoad += scriptUrls.length;
    }

    if(index >= scriptUrls.length) {
        if(onLoadCount >= totalOnLoad) {
            onLoadCount = 0;
            totalOnLoad = 0;
            callback();
        }
        return;
    }

    AppendScript(scriptUrls[index], () => {
        onLoadCount++;
        LoadScripts(scriptUrls, callback, index + 1);
    });
}

function LoadCSS(cssUrl) {
    var css = document.createElement("link");
    css.rel = "stylesheet";
    css.href = cssUrl;
    document.body.appendChild(css);
}

var loading = false;
var loaded = false;
var callbacks = [];
function AddDependencies(scriptFolder, callback) {
    if(loaded) {
        callback();
        return;
    }

    callbacks.push(callback);
    if(loading)
        return;

    loading = true;
    function onComplete() {
        loaded = true;
        callbacks.forEach(cb => cb());
        callbacks = [];
    }

    LoadScripts(["https://cdn.jsdelivr.net/gh/Microsoft/TypeScript@master/lib/typescriptServices.js"], onComplete);
    // LoadScripts(["https://unpkg.com/j-templates/jTemplates.js"], callback);
    LoadScripts([
        scriptFolder + "codemirror.js",
        scriptFolder + "javascript.js",
        // scriptFolder + "show-hint.js",
        // scriptFolder + "javascript-hint.js"
    ], onComplete);
    // LoadScript(scriptFolder + "javascript.js", callback);
    LoadCSS(scriptFolder + "styles/codemirror.css");
    LoadCSS(scriptFolder + "styles/styles.css");
    LoadCSS(scriptFolder + "styles/show-hint.css");
}

function HandleError(id, message, source, lineNo, colNo, error) {
    console.debug(arguments);
    var parentDoc = window.parent.document;
    var errorSpan = parentDoc.getElementById(id + "_sample");
    errorSpan.innerHTML = message;
    return false;
}

function ExecuteTs(id, code) {
    var container = document.getElementById(id + "_output");
    var errorSpan = document.getElementById(id + "_error");
    errorSpan.innerHTML = "";

    var iframe = container.querySelector("iframe");
    if(iframe)
        container.removeChild(iframe);

    iframe = document.createElement("iframe");
    iframe.className = "output";
    iframe.srcDoc = '<!DOCTYPE html>';
    container.appendChild(iframe);
    var jTempScript = iframe.contentDocument.createElement("script");
    jTempScript.type = "text/javascript";
    jTempScript.src = "https://unpkg.com/j-templates/jTemplates.js";
    jTempScript.onload = () => {
        var js = ts.transpile(code, { target: "es6" });
        js = js.replace(/^import.*$/gm, "");
        js = HandleError.toString() + '; var id="' + id + '";  var errorHandler = HandleError.bind(null, id); window.onerror = errorHandler; ' + js;
        var script = iframe.contentDocument.createElement("script");
        script.type = "text/javascript";
        script.innerHTML = js;
        iframe.contentDocument.head.appendChild(script);
    };
    iframe.contentDocument.head.appendChild(jTempScript);
}

var changeTimeout = null;
function CreateCodeMirror(id, initValue) {
    var container = document.getElementById(id + "_code");
    var cm = CodeMirror(container, {
        value: initValue,
        matchBrackets: true,
        lineNumbers: true,
        extraKeys: { "Ctrl-Space": "autocomplete" },
        mode: {name: "text/typescript", globalVars: false}
    });
    cm.on("change", () => {
        clearTimeout(changeTimeout);
        changeTimeout = setTimeout(() => {
            var div = document.getElementById(id + "_output");
            ExecuteTs(container.id, div, cm.getDoc().getValue());
        }, 4000);
    });
}

function CreateSample(sample) {
    var container = document.getElementById(sample);
    container.innerHTML = "Loading...";
    var curScriptUrl = document.querySelector("[src$='docHelpers.js'").src;
    var curScriptFolder = curScriptUrl.replace(/docHelpers\.js$/, "");
    AddDependencies(curScriptFolder, () => {
        var samplesFolder = curScriptFolder + "../samples/";
        var sampleUrl = samplesFolder + sample + ".ts";

        GetFile(sampleUrl, (text) => {
            container.innerHTML = "";

            var div = document.createElement("div");
            div.id = sample + "_code";
            div.className = "code";

            var h2 = document.createElement("h2");
            h2.innerText = "Code";
            var span = document.createElement("span");
            span.id = sample + "_error";
            span.className = "error";
            h2.appendChild(span);
            div.appendChild(h2);
            CreateCodeMirror(sample, text);
            container.appendChild(div);

            div = document.createElement("div");
            div.id = sample + "_output";
            div.className = "output";

            h2 = document.createElement("h2");
            h2.innerText = "Output";
            div.appendChild(h2);
            container.appendChild(div);
            ExecuteTs(sample, text);
        });
    });
}
<|endoftext|>


<commit_before>
var net = require('net');

var appc = null;
var dnsc = null;
var apps = null;
var dnss = {};

var dns = {};
var app = {};
var mode = null;
var help = null;
var id = 'default';
var rport = null;
var rapps = {};

for(var i=2; i<process.argv.length; i++) {
  var argv = process.argv[i];
  if(argv == '-d' || argv == '--dns'){
    i++;
    var parts = process.argv[i].split(':');
    dns = { host:parts[0], port:parseInt(parts[1]) || 53};
    continue;
  }
  if(argv == '-a' || argv == '--app'){
    i++;
    var parts = process.argv[i].split(':');
    app = { host:parts[0], port:parseInt(parts[1]) || 53};
    continue;
  }
  if(argv == '-i' || argv == '--id'){
    i++;
    id = process.argv[i];
    continue;
  }
  if(argv == '-r' || argv == '--rport'){
    i++;
    rport = parseInt(process.argv[i]) || 222;
    continue;
  }
  if(argv == '-c' || argv == '--client') mode = 'c';
  if(argv == '-s' || argv == '--server') mode = 's';
  if(argv == '-h' || argv == '--help') test = true;
}

if(help || process.argv.length < 6) {
  console.log({dns:dns, app:app, mode:mode});
  console.log(
    "Usage:\n"+
    "node dns_tcp_tunnel.js [-s|-c]  -d host:port -a host:port\n"+
    "\n"+
    "Options:\n"+
    "-i, --id     : Identifier\n"+
    "-s, --server : Start in server mode\n"+
    "-c, --client : Start in server mode\n"+
    "-d, --dns    : DNS server or dns listen host:port\n"+
    "-a, --app    : Application server or listen host:port\n"+
    "-r, --rport  : Remote port to request listen\n"+
    "-h, --help   : Print this info\n");

  process.exit(0);
}

console.log('Program started (id:'+id+')');

switch(mode) {
  case 's':
    start_server();
    break;
  case 'c':
    start_client();
    break;
  default:
    console.log("[s]erver or [c]lient?");
    break;
}
start_info_server();

function start_server() {
  console.log('starting server..');
  var server = net.createServer( function(_dnss) {
    console.log('a client is accepted');

    var rapp = {};

    _dnss.on('data', function(data) {
      console.log("dnss:received "+data.length+" bytes");

      var l = data.readUInt32BE(9);
      if(l == 16 && data.length == 29) {
        // client registration
        var parts = data.slice(13).toString().split(/\//);
        console.log("rapp = ",rapp);
        rapp = {
          id: parts[0],
          port: parts[1],
        }
        rapps[rapp.id+'_'+rapp.port] = JSON.parse(JSON.stringify(rapp));
        rapps[rapp.id+'_'+rapp.port].clients = {};
        rapp.dnss = _dnss;
        start_app_server(rapp);
        rapp.dnss.write(new Buffer('0000818000010001000000000c000c0001000100000000000420202020','hex'));
      }else{
        if(rapp.apps) {
          rapp.apps.write(data);
        }
      }
    });

    _dnss.on('end', function() {
      rapp.dnss = null;
      if(rapp.apps) {
        rapp.apps.end();
        rapp.apps = null;
      }
      if(rapp.server) {
        rapp.server.close();
      }
      delete rapps[rapp.id+'_'+rapp.port];
      rapp = null;
      console.log('dnss client disconnected');
    });
  });

  server.on('error', function(e) {
    console.log('dnss has an error, retrying');
    server.close();
    server.removeAllListeners();
    setTimeout( function() {
      start_server();
    },1000);
  });

  server.listen( dns.port, dns.host, function() {
    console.log("server is listening (dns) " + dns.host + ":" + dns.port);
  });
}

function start_app_server(rapp) {
  console.log('starting app server..');
  var server = net.createServer( function(_apps) {
    console.log('an app client for ('+rapp.id+'#'+rapp.port+')is accepted, ' + _apps.remoteAddress + ":" + _apps.remotePort);
    rapp.apps = _apps;

    var client_id = _apps.remoteAddress+'_'+_apps.remotePort;
    rapps[rapp.id+'_'+rapp.port].clients[client_id] = true;

    _apps.on('data', function(data) {
      console.log("apps:received "+data.length+" bytes");
      if(rapp.dnss) {
        rapp.dnss.write(data);
      }
    });

    _apps.on('end', function() {
      delete rapps[rapp.id+'_'+rapp.port].clients[client_id];
      rapp.apps = null;
      console.log('app client disconnected');
      if(rapp.dnss) {
        rapp.dnss.write(new Buffer("313370","hex"));
      }
    });
  });

  server.on('error', function(e) {
    delete rapps[rapp.id+'_'+rapp.port].clients[_apps.remoteAddress+'_'+_apps.remotePort];
    console.log('apps has an error, retrying',e);
    server.close();
    rapp.apps = null;
    rapp.server = null;
    delete rapps[rapp.id+'_'+rapp.port];
    server.removeAllListeners();
  });

  server.listen( rapp.port, "0.0.0.0", function() {
    console.log("server is listening (app) 0.0.0.0:" + rapp.port);
    rapp.server = server;
  });
}

function start_info_server() {
  console.log('starting info server..');
  var server = net.createServer( function(_is) {
    var report = JSON.stringify({
      dns:dns,
      app:app,
      mode:mode,
      id:id,
      rport:rport,
      rapps:rapps
    },null,"\t");
    _is.write(report+"\n");
    _is.end();
  });

  server.on('error', function(e) {
    console.log("error:",e);
  });

  server.listen( 2121, "0.0.0.0", function() {
    console.log("info server is listening 0.0.0.0:2121");
  });
}

function start_client() {
  console.log('starting client..');
  var _dnsc = net.connect( dns.port, dns.host, function() {
    console.log('dnsc connected');
    var header = new Buffer('00000100000100000000000000','hex');
    var idpadded = id+"/"+rport+"/";
    while(idpadded.length<16) idpadded = idpadded + "*";
    header.writeUInt32BE(idpadded.length, header.length - 4);
    var buffer = new Buffer(header.length + idpadded.length);
    header.copy(buffer);
    buffer.write(idpadded,header.length);
    _dnsc.write(buffer);
  });
  _dnsc.on('data', function(data) {
    console.log('dnsc recv ' + data.length + ' bytes');
    if(data.slice(0,3).toString("hex") == "313370") {
      console.log("App close cmd received");
      if(appc)
        appc.end();
      return;
    }
    if(dnsc==null){
      var l = data.readUInt32BE(9);
      console.log(l);
      console.log("dnsc: discarding first packet");
      console.log(data.toString("hex"));
      dnsc = _dnsc;
    }else{
      if(!appc) {
        connect_to_app(function() {
          appc.write(data);
        });
      }else{
        appc.write(data);
      }
    }
  });
  _dnsc.on('error', function(e) {
    console.log("dnsc: connection refused, retrying..");
    _dnsc.removeAllListeners();
    dnsc = null;
    setTimeout( function() {
      start_client();
    },1000);
  });
  _dnsc.on('end', function() {
    console.log('dnsc disconnected, reconnecting..');
    dnsc.removeAllListeners();
    dnsc = null;
    setTimeout( function() {
      start_client();
    },1000);
  });
}

function connect_to_app(cb) {
  console.log('starting appc..');
  var _appc = net.connect( app.port, app.host, function() {
    console.log('appc connected');
    appc = _appc;
    if(cb) cb();
  });
  _appc.on('data', function(data) {
    console.log('appc recv ' + data.length + ' bytes');
    if(dnsc) {
      dnsc.write(data);
    }else{
      _appc.end();
    }
  });
  _appc.on('error', function(e) {
    console.log("appc: connection refused, retrying..");
    appc = null;
    _appc.removeAllListeners();
  });
  _appc.on('end', function() {
    console.log('dnsc disconnected');
    appc = null;
  });
}
<commit_msg>added monitoring port cmd line option<commit_after>var net = require('net');

var appc = null;
var dnsc = null;
var apps = null;
var dnss = {};

var dns = {};
var app = {};
var mode = null;
var help = null;
var id = 'default';
var rport = null;
var mport = null;
var rapps = {};

for(var i=2; i<process.argv.length; i++) {
  var argv = process.argv[i];
  if(argv == '-d' || argv == '--dns'){
    i++;
    var parts = process.argv[i].split(':');
    dns = { host:parts[0], port:parseInt(parts[1]) || 53};
    continue;
  }
  if(argv == '-a' || argv == '--app'){
    i++;
    var parts = process.argv[i].split(':');
    app = { host:parts[0], port:parseInt(parts[1]) || 53};
    continue;
  }
  if(argv == '-i' || argv == '--id'){
    i++;
    id = process.argv[i];
    continue;
  }
  if(argv == '-r' || argv == '--rport'){
    i++;
    rport = parseInt(process.argv[i]) || 222;
    continue;
  }
  if(argv == '-m' || argv == '--mon'){
    i++;
    mport = parseInt(process.argv[i]) || 2121;
    continue;
  }
  if(argv == '-c' || argv == '--client') mode = 'c';
  if(argv == '-s' || argv == '--server') mode = 's';
  if(argv == '-h' || argv == '--help') test = true;
}

if(help || process.argv.length < 6) {
  console.log({dns:dns, app:app, mode:mode});
  console.log(
    "Usage:\n"+
    "node dns_tcp_tunnel.js [-s|-c]  -d host:port -a host:port\n"+
    "\n"+
    "Options:\n"+
    "-i, --id     : Identifier\n"+
    "-s, --server : Start in server mode\n"+
    "-c, --client : Start in server mode\n"+
    "-d, --dns    : DNS server or dns listen host:port\n"+
    "-a, --app    : Application server or listen host:port\n"+
    "-r, --rport  : Remote port to request listen\n"+
    "-m, --mon    : Monitoring service port\n"+
    "-h, --help   : Print this info\n");

  process.exit(0);
}

console.log('Program started (id:'+id+')');

switch(mode) {
  case 's':
    start_server();
    break;
  case 'c':
    start_client();
    break;
  default:
    console.log("[s]erver or [c]lient?");
    break;
}
start_info_server();

function start_server() {
  console.log('starting server..');
  var server = net.createServer( function(_dnss) {
    console.log('a client is accepted');

    var rapp = {};

    _dnss.on('data', function(data) {
      console.log("dnss:received "+data.length+" bytes");

      var l = data.readUInt32BE(9);
      if(l == 16 && data.length == 29) {
        // client registration
        var parts = data.slice(13).toString().split(/\//);
        console.log("rapp = ",rapp);
        rapp = {
          id: parts[0],
          port: parts[1],
        }
        rapps[rapp.id+'_'+rapp.port] = JSON.parse(JSON.stringify(rapp));
        rapp.dnss = _dnss;
        start_app_server(rapp);
        rapp.dnss.write(new Buffer('0000818000010001000000000c000c0001000100000000000420202020','hex'));
      }else{
        if(rapp.apps) {
          rapp.apps.write(data);
        }
      }
    });

    _dnss.on('end', function() {
      rapp.dnss = null;
      if(rapp.apps) {
        rapp.apps.end();
        rapp.apps = null;
      }
      if(rapp.server) {
        rapp.server.close();
      }
      delete rapps[rapp.id+'_'+rapp.port];
      rapp = null;
      console.log('dnss client disconnected');
    });
  });

  server.on('error', function(e) {
    console.log('dnss has an error, retrying');
    server.close();
    server.removeAllListeners();
    setTimeout( function() {
      start_server();
    },1000);
  });

  server.listen( dns.port, dns.host, function() {
    console.log("server is listening (dns) " + dns.host + ":" + dns.port);
  });
}

function start_app_server(rapp) {
  console.log('starting app server..');
  var server = net.createServer( function(_apps) {
    console.log('an app client for ('+rapp.id+'#'+rapp.port+')is accepted, ' + _apps.remoteAddress + ":" + _apps.remotePort);
    rapp.apps = _apps;

    rapps[rapp.id+'_'+rapp.port].clients[_apps.remoteAddress+'_'+_apps.remotePort] = true;

    _apps.on('data', function(data) {
      console.log("apps:received "+data.length+" bytes");
      if(rapp.dnss) {
        rapp.dnss.write(data);
      }
    });

    _apps.on('end', function() {
      delete rapps[rapp.id+'_'+rapp.port].clients[_apps.remoteAddress+'_'+_apps.remotePort];
      rapp.apps = null;
      console.log('app client disconnected');
      if(rapp.dnss) {
        rapp.dnss.write(new Buffer("313370","hex"));
      }
    });
  });

  server.on('error', function(e) {
    delete rapps[rapp.id+'_'+rapp.port].clients[_apps.remoteAddress+'_'+_apps.remotePort];
    console.log('apps has an error, retrying',e);
    server.close();
    rapp.apps = null;
    rapp.server = null;
    delete rapps[rapp.id+'_'+rapp.port];
    server.removeAllListeners();
  });

  server.listen( rapp.port, "0.0.0.0", function() {
    console.log("server is listening (app) 0.0.0.0:" + rapp.port);
    rapp.server = server;
  });
}

function start_info_server() {
  console.log('starting info server..');
  var server = net.createServer( function(_is) {
    var report = JSON.stringify({
      dns:dns,
      app:app,
      mode:mode,
      id:id,
      rport:rport,
      rapps:rapps
    },null,"\t");
    _is.write(report+"\n");
    _is.end();
  });

  server.on('error', function(e) {
    console.log("error:",e);
  });

  server.listen( mport, "0.0.0.0", function() {
    console.log("info server is listening 0.0.0.0:mport");
  });
}

function start_client() {
  console.log('starting client..');
  var _dnsc = net.connect( dns.port, dns.host, function() {
    console.log('dnsc connected');
    var header = new Buffer('00000100000100000000000000','hex');
    var idpadded = id+"/"+rport+"/";
    while(idpadded.length<16) idpadded = idpadded + "*";
    header.writeUInt32BE(idpadded.length, header.length - 4);
    var buffer = new Buffer(header.length + idpadded.length);
    header.copy(buffer);
    buffer.write(idpadded,header.length);
    _dnsc.write(buffer);
  });
  _dnsc.on('data', function(data) {
    console.log('dnsc recv ' + data.length + ' bytes');
    if(data.slice(0,3).toString("hex") == "313370") {
      console.log("App close cmd received");
      if(appc)
        appc.end();
      return;
    }
    if(dnsc==null){
      var l = data.readUInt32BE(9);
      console.log(l);
      console.log("dnsc: discarding first packet");
      console.log(data.toString("hex"));
      dnsc = _dnsc;
    }else{
      if(!appc) {
        connect_to_app(function() {
          appc.write(data);
        });
      }else{
        appc.write(data);
      }
    }
  });
  _dnsc.on('error', function(e) {
    console.log("dnsc: connection refused, retrying..");
    _dnsc.removeAllListeners();
    dnsc = null;
    setTimeout( function() {
      start_client();
    },1000);
  });
  _dnsc.on('end', function() {
    console.log('dnsc disconnected, reconnecting..');
    dnsc.removeAllListeners();
    dnsc = null;
    setTimeout( function() {
      start_client();
    },1000);
  });
}

function connect_to_app(cb) {
  console.log('starting appc..');
  var _appc = net.connect( app.port, app.host, function() {
    console.log('appc connected');
    appc = _appc;
    if(cb) cb();
  });
  _appc.on('data', function(data) {
    console.log('appc recv ' + data.length + ' bytes');
    if(dnsc) {
      dnsc.write(data);
    }else{
      _appc.end();
    }
  });
  _appc.on('error', function(e) {
    console.log("appc: connection refused, retrying..");
    appc = null;
    _appc.removeAllListeners();
  });
  _appc.on('end', function() {
    console.log('dnsc disconnected');
    appc = null;
  });
}

<|endoftext|>