<commit_before>//
// AWS Signature v4 Implementation for Web Browsers
//
// Copyright (c) 2016 Daniel Joos
//
// Distributed under MIT license. (See file LICENSE)
//

;(function (globals, factory) {
    globals.AwsSigner = factory();
})(this, function () {
    'use strict';

    var defaultConfig = {
        region: 'eu-west-1',
        service: 'execute-api',
        defaultContentType: 'application/json',
        defaultAcceptType: 'application/json',
        uriParserFactory: SimpleUriParser,
        hasherFactory: CryptoJSHasher
    };

    /**
     * Create a new signer object with the given configuration.
     * Configuration must specify the AWS credentials used for the signing operation.
     * It must contain the following properties:
     * `accessKeyId`: The AWS IAM access key ID.
     * `secretKey`: The AWS IAM secret key.
     * `sessionToken`: Optional session token, required for temporary credentials.
     * @param config The configuration object.
     * @constructor
     */
    var AwsSigner = function (config) {
        this.config = extend({}, defaultConfig, config);
        this.uriParser = this.config.uriParserFactory();
        this.hasher = this.config.hasherFactory();
        assertRequired(this.config.accessKeyId, 'Signer requires AWS AccessKeyID');
        assertRequired(this.config.secretKey, 'Signer requires AWS SecretKey');
    };

    /**
     * Create signature headers for the given request.
     * Request must be in the format, known from the `$http` service of Angular:
     * ```
     * request = {
     *      headers: { ... },
     *      method: 'GET',
     *      data: ...
     * };
     * ```
     * The resulting object contains the signature headers. For example, it can be merged into an
     * existing `$http` config when dealing with Angular JS.
     * @param request The request to create the signature for. Will not be modified!
     * @param signDate Optional signature date to use. Current date-time is used if not specified.
     * @returns Signed request headers.
     */
    AwsSigner.prototype.sign = function (request, signDate) {
        var workingSet = {
            request: extend({}, request),
            signDate: signDate || new Date(),
            uri: this.uriParser(request.url)
        };
        prepare(this, workingSet);
        buildCanonicalRequest(this, workingSet);    // Step1: build the canonical request
        buildStringToSign(this, workingSet);        // Step2: build the string to sign
        calculateSignature(this, workingSet);       // Step3: calculate the signature hash
        buildSignatureHeader(this, workingSet);     // Step4: build the authorization header
        return {
            'Accept': workingSet.request.headers['Accept'],
            'Authorization': workingSet.authorization,
            'x-amz-date': workingSet.request.headers['x-amz-date'],
            'x-amz-security-token': this.config.sessionToken || undefined
        };
    };

    // Some preparations
    function prepare(self, ws) {
        var headers = {
            'host': ws.uri.host,
            'content-type': self.config.defaultContentType,
            'accept': self.config.defaultAcceptType,
            'x-amz-date': amzDate(ws.signDate)
        };
        if (!ws.request.data) {
            delete headers['content-type'];
        } else {
            ws.request.data = JSON.stringify(ws.request.data);
        }
        ws.request.headers = extend(
            headers,
            Object.keys(ws.request.headers).reduce(function (normalized, key) {
                normalized[key.toLowerCase()] = ws.request.headers[key];
            }, {})
        );
        ws.sortedHeaderKeys = Object.keys(ws.request.headers).sort();
    }

    // Convert the request to a canonical format.
    function buildCanonicalRequest(self, ws) {
        ws.signedHeaders = ws.sortedHeaderKeys.map(function (key) {
            return key.toLowerCase();
        }).join(';');
        ws.canonicalRequest = String(ws.request.method).toUpperCase() + '\n' +
                // Canonical URI:
            encodeURI(ws.uri.path) + '\n' +
                // Canonical Query String:
            Object.keys(ws.uri.queryParams).sort().map(function (key) {
                return encodeURIComponent(key) + '=' +
                    encodeURIComponent(ws.uri.queryParams[key]);
            }).join('&') + '\n' +
                // Canonical Headers:
            ws.sortedHeaderKeys.map(function (key) {
                return key.toLocaleLowerCase() + ':' + ws.request.headers[key];
            }).join('\n') + '\n\n' +
                // Signed Headers:
            ws.signedHeaders + '\n' +
                // Hashed Payload
            self.hasher.hash((ws.request.data) ? ws.request.data : '');
    }

    // Construct the string that will be signed.
    function buildStringToSign(self, ws) {
        ws.credentialScope = [amzDate(ws.signDate, true), self.config.region, self.config.service,
            'aws4_request'].join('/');
        ws.stringToSign = 'AWS4-HMAC-SHA256' + '\n' +
            amzDate(ws.signDate) + '\n' +
            ws.credentialScope + '\n' +
            self.hasher.hash(ws.canonicalRequest);
    }

    // Calculate the signature
    function calculateSignature(self, ws) {
        var hmac = self.hasher.hmac;
        var signKey = hmac(
            hmac(
                hmac(
                    hmac(
                        'AWS4' + self.config.secretKey,
                        amzDate(ws.signDate, true),
                        {hexOutput: false}
                    ),
                    self.config.region,
                    {hexOutput: false, textInput: false}
                ),
                self.config.service,
                {hexOutput: false, textInput: false}
            ),
            'aws4_request',
            {hexOutput: false, textInput: false}
        );
        ws.signature = hmac(signKey, ws.stringToSign, {textInput: false});
    }

    // Build the signature HTTP header using the data in the working set.
    function buildSignatureHeader(self, ws) {
        ws.authorization = 'AWS4-HMAC-SHA256 ' +
            'Credential=' + self.config.accessKeyId + '/' + ws.credentialScope + ', ' +
            'SignedHeaders=' + ws.signedHeaders + ', ' +
            'Signature=' + ws.signature;
    }

    // Format the given `Date` as AWS compliant date string.
    // Time part gets omitted if second argument is set to `true`.
    function amzDate(date, short) {
        var result = date.toISOString().replace(/[:\-]|\.\d{3}/g, '').substr(0, 17);
        if (short) {
            return result.substr(0, 8);
        }
        return result;
    }

    /**
     * Simple URI parser factory.
     * Uses an `a` document element for parsing given URIs.
     * Therefore it most likely will only work in a web browser.
     */
    function SimpleUriParser() {
        var parser = document.createElement('a');

        /**
         * Parse the given URI.
         * @param uri The URI to parse.
         * @returns JavaScript object with the parse results:
         * `protocol`: The URI protocol part.
         * `host`: Host part of the URI.
         * `path`: Path part of the URI, always starting with a `/`
         * `queryParams`: Query parameters as JavaScript object.
         */
        return function (uri) {
            parser.href = uri;
            return {
                protocol: parser.protocol,
                host: parser.host,
                path: ((parser.pathname.charAt(0) !== '/') ? '/' : '') + parser.pathname,
                queryParams: extractQueryParams(parser.search)
            };
        };

        function extractQueryParams(search) {
            return /^\??(.*)$/.exec(search)[1].split('&').reduce(function (result, arg) {
                arg = /^(.+)=(.*)$/.exec(arg);
                if (arg) {
                    result[arg[1]] = arg[2];
                }
                return result;
            }, {});
        }
    }

    /**
     * Hash factory implementation using the SHA-256 hash algorithm of CryptoJS.
     * Requires at least the CryptoJS rollups: `sha256.js` and `hmac-sha256.js`.
     */
    function CryptoJSHasher() {
        /* global CryptoJS */
        return {
            /**
             * Hash the given input using SHA-256 algorithm.
             * The options can be used to control the in-/output of the hash operation.
             * @param input Input data.
             * @param options Options object:
             * `hexOutput` -- Output the hash with hex encoding (default: `true`).
             * `textInput` -- Interpret the input data as text (default: `true`).
             * @returns The generated hash
             */
            hash: function (input, options) {
                options = extend({hexOutput: true, textInput: true}, options);
                var hash = CryptoJS.SHA256(input);
                if (options.hexOutput) {
                    return hash.toString(CryptoJS.enc.Hex);
                }
                return hash;
            },

            /**
             * Create the HMAC of the given input data with the given key using the SHA-256
             * hash algorithm.
             * The options can be used to control the in-/output of the hash operation.
             * @param key Secret key.
             * @param input Input data.
             * @param options Options object:
             * `hexOutput` -- Output the hash with hex encoding (default: `true`).
             * `textInput` -- Interpret the input data as text (default: `true`).
             * @returns The generated HMAC.
             */
            hmac: function (key, input, options) {
                options = extend({hexOutput: true, textInput: true}, options);
                var hmac = CryptoJS.HmacSHA256(input, key, {asBytes: true});
                if (options.hexOutput) {
                    return hmac.toString(CryptoJS.enc.Hex);
                }
                return hmac;
            }
        };
    }

    // Simple version of the `extend` function, known from Angular and Backbone.
    // It merges the second (and all succeeding) argument(s) into the object, given as first
    // argument. This is done recursively for all child objects, as well.
    function extend(dest) {
        var objs = [].slice.call(arguments, 1);
        objs.forEach(function (obj) {
            if (!obj || typeof(obj) !== 'object') {
                return;
            }
            Object.keys(obj).forEach(function (key) {
                var src = obj[key];
                if (typeof(src) === 'undefined') {
                    return;
                }
                if (typeof(src) === 'object') {
                    dest[key] = extend({}, src);
                } else {
                    dest[key] = src;
                }
            });
        });
        return dest;
    }

    // Throw an error if the given object is undefined.
    function assertRequired(obj, msg) {
        if (typeof(obj) === 'undefined' || !obj) {
            throw new Error(msg);
        }
    }

    return AwsSigner;
});
<commit_msg>Fixed accept header in result object<commit_after>//
// AWS Signature v4 Implementation for Web Browsers
//
// Copyright (c) 2016 Daniel Joos
//
// Distributed under MIT license. (See file LICENSE)
//

;(function (globals, factory) {
    globals.AwsSigner = factory();
})(this, function () {
    'use strict';

    var defaultConfig = {
        region: 'eu-west-1',
        service: 'execute-api',
        defaultContentType: 'application/json',
        defaultAcceptType: 'application/json',
        uriParserFactory: SimpleUriParser,
        hasherFactory: CryptoJSHasher
    };

    /**
     * Create a new signer object with the given configuration.
     * Configuration must specify the AWS credentials used for the signing operation.
     * It must contain the following properties:
     * `accessKeyId`: The AWS IAM access key ID.
     * `secretKey`: The AWS IAM secret key.
     * `sessionToken`: Optional session token, required for temporary credentials.
     * @param config The configuration object.
     * @constructor
     */
    var AwsSigner = function (config) {
        this.config = extend({}, defaultConfig, config);
        this.uriParser = this.config.uriParserFactory();
        this.hasher = this.config.hasherFactory();
        assertRequired(this.config.accessKeyId, 'Signer requires AWS AccessKeyID');
        assertRequired(this.config.secretKey, 'Signer requires AWS SecretKey');
    };

    /**
     * Create signature headers for the given request.
     * Request must be in the format, known from the `$http` service of Angular:
     * ```
     * request = {
     *      headers: { ... },
     *      method: 'GET',
     *      data: ...
     * };
     * ```
     * The resulting object contains the signature headers. For example, it can be merged into an
     * existing `$http` config when dealing with Angular JS.
     * @param request The request to create the signature for. Will not be modified!
     * @param signDate Optional signature date to use. Current date-time is used if not specified.
     * @returns Signed request headers.
     */
    AwsSigner.prototype.sign = function (request, signDate) {
        var workingSet = {
            request: extend({}, request),
            signDate: signDate || new Date(),
            uri: this.uriParser(request.url)
        };
        prepare(this, workingSet);
        buildCanonicalRequest(this, workingSet);    // Step1: build the canonical request
        buildStringToSign(this, workingSet);        // Step2: build the string to sign
        calculateSignature(this, workingSet);       // Step3: calculate the signature hash
        buildSignatureHeader(this, workingSet);     // Step4: build the authorization header
        return {
            'Accept': workingSet.request.headers['accept'],
            'Authorization': workingSet.authorization,
            'x-amz-date': workingSet.request.headers['x-amz-date'],
            'x-amz-security-token': this.config.sessionToken || undefined
        };
    };

    // Some preparations
    function prepare(self, ws) {
        var headers = {
            'host': ws.uri.host,
            'content-type': self.config.defaultContentType,
            'accept': self.config.defaultAcceptType,
            'x-amz-date': amzDate(ws.signDate)
        };
        if (!ws.request.data) {
            delete headers['content-type'];
        } else {
            ws.request.data = JSON.stringify(ws.request.data);
        }
        ws.request.headers = extend(
            headers,
            Object.keys(ws.request.headers).reduce(function (normalized, key) {
                normalized[key.toLowerCase()] = ws.request.headers[key];
            }, {})
        );
        ws.sortedHeaderKeys = Object.keys(ws.request.headers).sort();
    }

    // Convert the request to a canonical format.
    function buildCanonicalRequest(self, ws) {
        ws.signedHeaders = ws.sortedHeaderKeys.map(function (key) {
            return key.toLowerCase();
        }).join(';');
        ws.canonicalRequest = String(ws.request.method).toUpperCase() + '\n' +
                // Canonical URI:
            encodeURI(ws.uri.path) + '\n' +
                // Canonical Query String:
            Object.keys(ws.uri.queryParams).sort().map(function (key) {
                return encodeURIComponent(key) + '=' +
                    encodeURIComponent(ws.uri.queryParams[key]);
            }).join('&') + '\n' +
                // Canonical Headers:
            ws.sortedHeaderKeys.map(function (key) {
                return key.toLocaleLowerCase() + ':' + ws.request.headers[key];
            }).join('\n') + '\n\n' +
                // Signed Headers:
            ws.signedHeaders + '\n' +
                // Hashed Payload
            self.hasher.hash((ws.request.data) ? ws.request.data : '');
    }

    // Construct the string that will be signed.
    function buildStringToSign(self, ws) {
        ws.credentialScope = [amzDate(ws.signDate, true), self.config.region, self.config.service,
            'aws4_request'].join('/');
        ws.stringToSign = 'AWS4-HMAC-SHA256' + '\n' +
            amzDate(ws.signDate) + '\n' +
            ws.credentialScope + '\n' +
            self.hasher.hash(ws.canonicalRequest);
    }

    // Calculate the signature
    function calculateSignature(self, ws) {
        var hmac = self.hasher.hmac;
        var signKey = hmac(
            hmac(
                hmac(
                    hmac(
                        'AWS4' + self.config.secretKey,
                        amzDate(ws.signDate, true),
                        {hexOutput: false}
                    ),
                    self.config.region,
                    {hexOutput: false, textInput: false}
                ),
                self.config.service,
                {hexOutput: false, textInput: false}
            ),
            'aws4_request',
            {hexOutput: false, textInput: false}
        );
        ws.signature = hmac(signKey, ws.stringToSign, {textInput: false});
    }

    // Build the signature HTTP header using the data in the working set.
    function buildSignatureHeader(self, ws) {
        ws.authorization = 'AWS4-HMAC-SHA256 ' +
            'Credential=' + self.config.accessKeyId + '/' + ws.credentialScope + ', ' +
            'SignedHeaders=' + ws.signedHeaders + ', ' +
            'Signature=' + ws.signature;
    }

    // Format the given `Date` as AWS compliant date string.
    // Time part gets omitted if second argument is set to `true`.
    function amzDate(date, short) {
        var result = date.toISOString().replace(/[:\-]|\.\d{3}/g, '').substr(0, 17);
        if (short) {
            return result.substr(0, 8);
        }
        return result;
    }

    /**
     * Simple URI parser factory.
     * Uses an `a` document element for parsing given URIs.
     * Therefore it most likely will only work in a web browser.
     */
    function SimpleUriParser() {
        var parser = document.createElement('a');

        /**
         * Parse the given URI.
         * @param uri The URI to parse.
         * @returns JavaScript object with the parse results:
         * `protocol`: The URI protocol part.
         * `host`: Host part of the URI.
         * `path`: Path part of the URI, always starting with a `/`
         * `queryParams`: Query parameters as JavaScript object.
         */
        return function (uri) {
            parser.href = uri;
            return {
                protocol: parser.protocol,
                host: parser.host,
                path: ((parser.pathname.charAt(0) !== '/') ? '/' : '') + parser.pathname,
                queryParams: extractQueryParams(parser.search)
            };
        };

        function extractQueryParams(search) {
            return /^\??(.*)$/.exec(search)[1].split('&').reduce(function (result, arg) {
                arg = /^(.+)=(.*)$/.exec(arg);
                if (arg) {
                    result[arg[1]] = arg[2];
                }
                return result;
            }, {});
        }
    }

    /**
     * Hash factory implementation using the SHA-256 hash algorithm of CryptoJS.
     * Requires at least the CryptoJS rollups: `sha256.js` and `hmac-sha256.js`.
     */
    function CryptoJSHasher() {
        /* global CryptoJS */
        return {
            /**
             * Hash the given input using SHA-256 algorithm.
             * The options can be used to control the in-/output of the hash operation.
             * @param input Input data.
             * @param options Options object:
             * `hexOutput` -- Output the hash with hex encoding (default: `true`).
             * `textInput` -- Interpret the input data as text (default: `true`).
             * @returns The generated hash
             */
            hash: function (input, options) {
                options = extend({hexOutput: true, textInput: true}, options);
                var hash = CryptoJS.SHA256(input);
                if (options.hexOutput) {
                    return hash.toString(CryptoJS.enc.Hex);
                }
                return hash;
            },

            /**
             * Create the HMAC of the given input data with the given key using the SHA-256
             * hash algorithm.
             * The options can be used to control the in-/output of the hash operation.
             * @param key Secret key.
             * @param input Input data.
             * @param options Options object:
             * `hexOutput` -- Output the hash with hex encoding (default: `true`).
             * `textInput` -- Interpret the input data as text (default: `true`).
             * @returns The generated HMAC.
             */
            hmac: function (key, input, options) {
                options = extend({hexOutput: true, textInput: true}, options);
                var hmac = CryptoJS.HmacSHA256(input, key, {asBytes: true});
                if (options.hexOutput) {
                    return hmac.toString(CryptoJS.enc.Hex);
                }
                return hmac;
            }
        };
    }

    // Simple version of the `extend` function, known from Angular and Backbone.
    // It merges the second (and all succeeding) argument(s) into the object, given as first
    // argument. This is done recursively for all child objects, as well.
    function extend(dest) {
        var objs = [].slice.call(arguments, 1);
        objs.forEach(function (obj) {
            if (!obj || typeof(obj) !== 'object') {
                return;
            }
            Object.keys(obj).forEach(function (key) {
                var src = obj[key];
                if (typeof(src) === 'undefined') {
                    return;
                }
                if (typeof(src) === 'object') {
                    dest[key] = extend({}, src);
                } else {
                    dest[key] = src;
                }
            });
        });
        return dest;
    }

    // Throw an error if the given object is undefined.
    function assertRequired(obj, msg) {
        if (typeof(obj) === 'undefined' || !obj) {
            throw new Error(msg);
        }
    }

    return AwsSigner;
});
<|endoftext|>


<commit_before>//Translated from connector.rb using babyruby2js
'use strict';

var main = require('../main');
var Grid = require('../Grid');
var Heuristic = require('./Heuristic');
var inherits = require('util').inherits;

/** @class A move that connects 2 of our groups is good.
 */
function Connector(player) {
    Heuristic.call(this, player);
    this.inflCoeff = this.getGene('infl', 0.07, 0.01, 0.5);
    this.allyCoeff1 = this.getGene('ally-1enemy', 0.33, 0.01, 1.0);
    this.allyCoeff2 = this.getGene('ally-more-enemies', 1.66, 0.01, 3.0);
}
inherits(Connector, Heuristic);
module.exports = Connector;

Connector.prototype.evalMove = function (i, j) {
    return this.connectsMyGroups(i, j, this.color) +
           this.connectsMyGroups(i, j, 1 - this.color);
};

Connector.prototype.connectsMyGroups = function (i, j, color) {
    // TODO: one other way to connect 2 groups is to "protect" the cutting point; handle this here
    var stone = this.goban.stoneAt(i, j);
    var s1, s1b, s2, s2b, s3;
    var numStones = 0, numEnemies = 0;
    for (var n = stone.neighbors.length - 1; n >= 0; n--) {
        var s = stone.neighbors[n];
        switch (s.color) {
        case main.EMPTY: continue;
        case color:
            numStones++;
            if (!s1) {
                s1 = s;
            } else if (!s2) {
                if (s.group !== s1.group) s2 = s; else s1b = s;
            } else {
                if (s.group !== s2.group) s3 = s; else s2b = s;
            }
            break;
        default: numEnemies++;
        }
    }
    if (!s2) return 0; // nothing to connect here
    if (numStones === 4) return 0; // 1 empty between 4 stones; never connect unless forced to
    // 3 of our stones around: no need to connect unless enemy comes by
    if (numStones === 3 && numEnemies === 0) return 0;

    var numGroups = s3 ? 3 : 2;
    // if 3rd stone in same group than 1 or 2; we keep the diagonal ones
    if (numGroups === 2 && numStones === 3) {
        if (s2b) { s1b = s2b; var swap = s1; s1 = s2; s2 = swap; }
        if (s1.i === s2.i || s1.j === s2.j) s1 = s1b;
    }
    // Case of diagonal (strong) stones
    if (s1.i !== s2.i && s1.j !== s2.j) {
        // No need to connect if both connection points are free (no cutting stone yet)
        if (this.goban.isEmpty(s1.i, s2.j) && this.goban.isEmpty(s2.i, s1.j)) return 0;
        // We count the cutting stone as enemy (we did not "see" it above because it's diagonal)
        numEnemies++;
    }
    var score;
    if (numEnemies === 0) {
        score = this.inflCoeff / this.inf.map[j][i][color];
    } else {
        score = this.allyCoeff1 * numGroups;
    }
    if (main.debug) {
        main.log.debug('Connector for ' + Grid.colorName(color) + ' gives ' + score.toFixed(3) + ' to ' + i + ',' + j +
            ' (allies:' + numGroups + ' enemies: ' + numEnemies + ')');
    }
    return score;
};
<commit_msg>AI: Connector does not worry anymore on border vertex<commit_after>//Translated from connector.rb using babyruby2js
'use strict';

var main = require('../main');
var Grid = require('../Grid');
var Heuristic = require('./Heuristic');
var inherits = require('util').inherits;

var EMPTY = main.EMPTY;

/** @class A move that connects 2 of our groups is good.
 */
function Connector(player) {
    Heuristic.call(this, player);
    this.inflCoeff = this.getGene('infl', 0.07, 0.01, 0.5);
    this.allyCoeff1 = this.getGene('ally-1enemy', 0.33, 0.01, 1.0);
    this.allyCoeff2 = this.getGene('ally-more-enemies', 1.66, 0.01, 3.0);
}
inherits(Connector, Heuristic);
module.exports = Connector;

Connector.prototype.evalMove = function (i, j) {
    return this.connectsMyGroups(i, j, this.color) +
           this.connectsMyGroups(i, j, 1 - this.color);
};

Connector.prototype.connectsMyGroups = function (i, j, color) {
    // TODO: one other way to connect 2 groups is to "protect" the cutting point; handle this here
    var stone = this.goban.stoneAt(i, j);
    var s1, s1b, s2, s2b, s3;
    var numStones = 0, numEnemies = 0;
    for (var n = stone.neighbors.length - 1; n >= 0; n--) {
        var s = stone.neighbors[n];
        switch (s.color) {
        case main.EMPTY: continue;
        case color:
            numStones++;
            if (!s1) {
                s1 = s;
            } else if (!s2) {
                if (s.group !== s1.group) s2 = s; else s1b = s;
            } else {
                if (s.group !== s2.group) s3 = s; else s2b = s;
            }
            break;
        default: numEnemies++;
        }
    }
    if (!s2) return 0; // nothing to connect here
    if (numStones === 4) return 0; // 1 empty between 4 stones; never connect unless forced to
    // 3 of our stones around: no need to connect unless enemy comes by or threatens
    if (numStones === 3 && numEnemies === 0 &&
        s1.group.lives > 1 && s2.group.lives > 1 && (!s3 || s3.group.lives > 1)) return 0;

    var numGroups = s3 ? 3 : 2;
    // if 3rd stone in same group than 1 or 2; we keep the diagonal ones
    if (numGroups === 2 && numStones === 3) {
        if (s2b) { s1b = s2b; var swap = s1; s1 = s2; s2 = swap; }
        if (s1.i === s2.i || s1.j === s2.j) s1 = s1b;
    }
    // Case of diagonal (strong) stones
    if (s1.i !== s2.i && s1.j !== s2.j) {
        // No need to connect if both connection points are free (no cutting stone yet)
        var c1 = this.goban.stoneAt(s1.i, s2.j), c2 = this.goban.stoneAt(s2.i, s1.j);
        if (c1.color === EMPTY && c2.color === EMPTY) return 0;
        // No need to connect on a border stone
        if (this.distanceFromStoneToBorder(c1) === 0 && c1.color === EMPTY) return 0;
        if (this.distanceFromStoneToBorder(c2) ===0 && c2.color === EMPTY) return 0;
        // We count the cutting stone as enemy (we did not "see" it above because it's diagonal)
        numEnemies++;
    }
    var score;
    if (numEnemies === 0) {
        score = this.inflCoeff / this.inf.map[j][i][color];
    } else {
        score = this.allyCoeff1 * numGroups;
    }
    if (main.debug) main.log.debug('Connector for ' + Grid.colorName(color) + ' gives ' + score.toFixed(3) + ' to ' + i + ',' + j +
        ' (allies:' + numGroups + ' enemies: ' + numEnemies + ')');
    return score;
};
<|endoftext|>


<commit_before>import Mailgun from 'mailgun-js'
import {EmailTemplate} from 'email-templates'
import Handlebars from 'handlebars'
import path from 'path'
import fs from 'fs'
import log from './log'

const templateDir = path.resolve(__dirname, './email-templates')
const headerHTML = fs.readFileSync(templateDir + '/header.hbs', {encoding: 'utf-8'})
const footerHTML = fs.readFileSync(templateDir + '/footer.hbs', {encoding: 'utf-8'})

Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
  switch (operator) {
    case '==':
      return (v1 == v2) ? options.fn(this) : options.inverse(this)
    case '===':
      return (v1 === v2) ? options.fn(this) : options.inverse(this)
    case '<':
      return (v1 < v2) ? options.fn(this) : options.inverse(this)
    case '<=':
      return (v1 <= v2) ? options.fn(this) : options.inverse(this)
    case '>':
      return (v1 > v2) ? options.fn(this) : options.inverse(this)
    case '>=':
      return (v1 >= v2) ? options.fn(this) : options.inverse(this)
    case '&&':
      return (v1 && v2) ? options.fn(this) : options.inverse(this)
    case '||':
      return (v1 || v2) ? options.fn(this) : options.inverse(this)
    default:
      return options.inverse(this)
  }
})

Handlebars.registerPartial('header', headerHTML)
Handlebars.registerPartial('footer', footerHTML)

const senderAddress = 'Team Bernie<info@berniesanders.com>'

export default class MG {
  constructor(apiKey, domain) {
    this.mailgun = Mailgun({apiKey: apiKey, domain: domain})
  }

  async sendEventConfirmation(form, constituent, event_types, debugging) {
    if (form.capacity =='0') {
      form.capacity = 'unlimited'
    }

    // Sort event dates by date
    if (typeof form.event_dates == 'string') {
      form.event_dates = JSON.parse(form.event_dates)
    }

    form.event_dates.sort((a, b) => {
        return a.date.localeCompare(b.date)
    })

    // Get the event type name
    event_types.forEach((type) => {
      if (type.event_type_id == form.event_type_id) {
        form.event_type_name = type.name
      }
    })

    constituent.cons_email.forEach((email) => {
      if (email.is_primary == '1') {
        constituent['email'] = email.email
      }
    })

    let data = {
      event: form,
      user: constituent
    }

    let eventConfirmation = new EmailTemplate(templateDir + '/event-create-confirmation')
    let content = await eventConfirmation.render(data)

    let message = {
      from: senderAddress,
      to: form.cons_email,
      subject: 'Event Creation Confirmation',
      text: content.text,
      html: content.html
    }

    log.debug(message)

    if (debugging) {
      return message
    } else {
      return await this.mailgun.messages().send(message)
    }
  }

  async sendPhoneBankConfirmation(form, constituent, debugging) {
    constituent.cons_email.forEach((email) => {
      if (email.is_primary == '1') {
        constituent['email'] = email.email
      }
    })

    let data = {
      event: form,
      user: constituent
    }

    let template = new EmailTemplate(templateDir + '/event-create-confirmation')
    let content = await template.render(data)

    let message = {
      from: senderAddress,
      'h:Reply-To': 'help@berniesanders.com',
      to: form.cons_email,
      subject: 'Event Creation Confirmation',
      text: content.text,
      html: content.html
    }

    if (debugging) {
      return message
    } else {
      return await this.mailgun.messages().send(message)
    }
  }

  async sendAdminEventInvite(data, debugging) {
    let template = new EmailTemplate(templateDir + '/admin-event-invitation')
    let content = await template.render(data)

    let message = {
      from: data.senderAddress,
      'h:Reply-To': data.hostAddress,
      to: data.recipientAddress,
      subject: 'Fwd: HELP! I need more people to come to my phonebank',
      text: content.text
    }

    log.info("Sending Mailgun message", message)

    if (debugging) {
      return message
    } else {
      return await this.mailgun.messages().send(message)
    }
  }
}
<commit_msg>Consolidate debugging logic in MG class<commit_after>import Mailgun from 'mailgun-js'
import {EmailTemplate} from 'email-templates'
import Handlebars from 'handlebars'
import path from 'path'
import fs from 'fs'
import log from './log'

const templateDir = path.resolve(__dirname, './email-templates')
const headerHTML = fs.readFileSync(templateDir + '/header.hbs', {encoding: 'utf-8'})
const footerHTML = fs.readFileSync(templateDir + '/footer.hbs', {encoding: 'utf-8'})

Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
  switch (operator) {
    case '==':
      return (v1 == v2) ? options.fn(this) : options.inverse(this)
    case '===':
      return (v1 === v2) ? options.fn(this) : options.inverse(this)
    case '<':
      return (v1 < v2) ? options.fn(this) : options.inverse(this)
    case '<=':
      return (v1 <= v2) ? options.fn(this) : options.inverse(this)
    case '>':
      return (v1 > v2) ? options.fn(this) : options.inverse(this)
    case '>=':
      return (v1 >= v2) ? options.fn(this) : options.inverse(this)
    case '&&':
      return (v1 && v2) ? options.fn(this) : options.inverse(this)
    case '||':
      return (v1 || v2) ? options.fn(this) : options.inverse(this)
    default:
      return options.inverse(this)
  }
})

Handlebars.registerPartial('header', headerHTML)
Handlebars.registerPartial('footer', footerHTML)

const senderAddress = 'Team Bernie<info@berniesanders.com>'
const inDevEnv = (process.env.NODE_ENV === 'development')

export default class MG {
  constructor(apiKey, domain) {
    this.mailgun = Mailgun({apiKey: apiKey, domain: domain})
  }

  async send(message, debugging) {
    log.info("Sending email via Mailgun", message)

    if (inDevEnv || debugging) {
      return message
    } else {
      return await this.mailgun.messages().send(message)
    }
  }

  async sendEventConfirmation(form, constituent, event_types, debugging) {
    if (form.capacity === '0') {
      form.capacity = 'unlimited'
    }

    // Sort event dates by date
    if (typeof form.event_dates === 'string') {
      form.event_dates = JSON.parse(form.event_dates)
    }

    form.event_dates.sort((a, b) => {
      return a.date.localeCompare(b.date)
    })

    // Get the event type name
    event_types.forEach((type) => {
      if (type.event_type_id == form.event_type_id) {
        form.event_type_name = type.name
      }
    })

    constituent.cons_email.forEach((email) => {
      if (email.is_primary == '1') {
        constituent['email'] = email.email
      }
    })

    let data = {
      event: form,
      user: constituent
    }

    let eventConfirmation = new EmailTemplate(templateDir + '/event-create-confirmation')
    let content = await eventConfirmation.render(data)

    let message = {
      from: senderAddress,
      to: form.cons_email,
      subject: 'Event Creation Confirmation',
      text: content.text,
      html: content.html
    }

    return await send(message, debugging)
  }

  async sendPhoneBankConfirmation(form, constituent, debugging) {
    constituent.cons_email.forEach((email) => {
      if (email.is_primary === '1') {
        constituent['email'] = email.email
      }
    })

    let data = {
      event: form,
      user: constituent
    }

    let template = new EmailTemplate(templateDir + '/event-create-confirmation')
    let content = await template.render(data)

    let message = {
      from: senderAddress,
      'h:Reply-To': 'help@berniesanders.com',
      to: form.cons_email,
      subject: 'Event Creation Confirmation',
      text: content.text,
      html: content.html
    }

    return await send(message, debugging)
  }

  async sendAdminEventInvite(data, debugging) {
    let template = new EmailTemplate(templateDir + '/admin-event-invitation')
    let content = await template.render(data)

    let message = {
      from: data.senderAddress,
      'h:Reply-To': data.hostAddress,
      to: data.recipientAddress,
      subject: 'Fwd: HELP! I need more people to come to my phonebank',
      text: content.text
    }

    return await send(message, debugging)
  }
}
<|endoftext|>


<commit_before>'use-strict'

var hookshot = require('hookshot'),
	fs         = require('fs'),
	sh         = require('execSync'),
	request    = require('request'),
	colors     = require('colors');

var config      = require('../config.json'),
		sh_commands = require('./sh-commands.js');

function verifyAccount(incoming_repo){
	if (incoming_repo == config.github_listener.account_name) return true;
	return false;
}
function checkIfCommitterIsDeployer(members, committer){
	members.some(function(member){
		return member.name === committer;
	})
}
function verifyCommitter(last_commit, cb){
	// You only need to verify the deployer if you're using teams, otherwise disable it and always allow anyone pushing to that repo to deploy.
	if (!config.verify_committer.enabled){
		cb(true);
	}else{
		var committer = last_commit.committer.username;
		request('https://api.github.com/teams/' + config.verify_committer.team_id + '/members?access_token=' + config.verify_committer.access_token, function (error, response, body) {
		  if (!error && response.statusCode == 200) {
		  	var committer_is_deployer = checkIfCommitterIsDeployer(JSON.parse(body), committer);
		    cb(committer_is_deployer);
		  } else {
		  	console.log('Error verifying committer'.red, JSON.stringify(error))
		  }
		})
	}
}

function createDirGitInit(info){
	var repo_name = info.repository.name;

	fs.mkdirSync('./repositories/' + repo_name);

	var remote_url_arr = info.repository.url.split('//');
	var authenticated_remote_url = url_arr[0] + '//' + config.verify_committer.access_token + '@' + remote_url_arr[1];
	var create_statement = sh_commands.createGitRepoAndRemotes(repo_name, authenticated_remote_url);
  sh.run(create_statement);
}
function pullLatest(info){
	var repo_name = info.repository.name,
		branch_name = info.ref.split('/')[2], // `ref: "refs/heads/<branchname>` => `branchname`
		delete_branch;

	if (!fs.existsSync('./repositories/' + repo_name)){
		createDirGitInit(info);
	}

	// Download latest data
	var fetch_statement = sh_commands.fetchLatest(repo_name);
	sh.run(fetch_statement);

	// If it's deleted, delete it!
	if (info.deleted) {
		delete_branch = sh_commands.deleteBranch(repo_name, branch_name);
		sh.run(delete_branch);
	}

	// Update all branches
	var track_all_branches = sh_commands.trackAllBranches(repo_name);
	sh.run(track_all_branches);

	// Put the working tree back on to master
	var checkout_master = sh_commands.checkoutMaster(repo_name);
	sh.run(checkout_master);
}
function checkForDeployMsg(last_commit){
	var commit_msg = last_commit.message,
	    cp_deploy_regx   = new RegExp(config.s3.hard_deploy.trigger),
	    sync_deploy_regx = new RegExp(config.s3.sync_deploy_trigger);

	if (config.s3.hard_deploy.enabled && cp_deploy_regx.exec(commit_msg)) return 'cp';
	if (sync_deploy_regx.exec(commit_msg)) return 'sync';
	return false;
}
function deployToS3(deploy_type, most_recent_commit){
	var repo_name   = info.repository.name,
			local_path  = most_recent_commit.split('::')[1], // `published::output` -> `output` or `published::` -> `''`
	    remote_path = config.s3.path || '';

	// If a local path was specified, put a slash in front of it
	if (local_path) local_path = '/' + local_path;

	var deploy_statement = sh_commands.deploy(deploy_type, repo_name, config.s3.bucket_name, local_path, remote_path, config.s3.exclude);
	var deploy_result = sh.exec(deploy_statement);
	// Log deployment result
	console.log(deploy_result.stdout);
}

hookshot(function(info){
	// Is this request coming from the specified GitHub Account?
	var is_account_verified = verifyAccount(info.repository.owner.name);
	// Is there a deploy message present?
	var most_recent_commit,
			deploy_status;
	// The last commit in the array is the most recent
	// But `info.commits` will be an empty array if you pushed a new branch with no commits
	if (info.commits.length) {
		most_recent_commit  = info.commits[info.commits.length - 1];
		deploy_status       = checkForDeployMsg(most_recent_commit);
	}
	// Is this coming from the whitelisted GitHub account?
	if (is_account_verified){
		pullLatest(info);

		// Are we deploying? Has that option been enabled and does the commit have the appropriate message?
		if (config.s3.enabled && deploy_status){
			verifyCommitter(most_recent_commit, function(committer_approved){

				// Does the committer have deploy? privileges?
				if (committer_approved) {
					deployToS3(deploy_status, most_recent_commit);
				} else {
					console.log('Unapproved committer attempted deployment.'.red)
				}

			});
		}
	}
}).listen(config.github_listener.port);

console.log('Listening on port... ' + config.github_listener.port);	<commit_msg>not so stringent commands for request reponse<commit_after>'use-strict'

var hookshot = require('hookshot'),
	fs         = require('fs'),
	sh         = require('execSync'),
	request    = require('request'),
	colors     = require('colors');

var config      = require('../config.json'),
		sh_commands = require('./sh-commands.js');

function verifyAccount(incoming_repo){
	if (incoming_repo == config.github_listener.account_name) return true;
	return false;
}
function checkIfCommitterIsDeployer(members, committer){
	members.some(function(member){
		return member.name === committer;
	})
}
function verifyCommitter(last_commit, cb){
	// You only need to verify the deployer if you're using teams, otherwise disable it and always allow anyone pushing to that repo to deploy.
	if (!config.verify_committer.enabled){
		cb(true);
	}else{
		var committer = last_commit.committer.username;
		request('https://api.github.com/teams/' + config.verify_committer.team_id + '/members?access_token=' + config.verify_committer.access_token, function (error, response, body) {
		  if (!error) {
		  	var committer_is_deployer = checkIfCommitterIsDeployer(JSON.parse(body), committer);
		    cb(committer_is_deployer);
		  } else {
		  	console.log('Error verifying committer'.red, JSON.stringify(error))
		  }
		})
	}
}

function createDirGitInit(info){
	var repo_name = info.repository.name;

	fs.mkdirSync('./repositories/' + repo_name);

	var remote_url_arr = info.repository.url.split('//');
	var authenticated_remote_url = url_arr[0] + '//' + config.verify_committer.access_token + '@' + remote_url_arr[1];
	var create_statement = sh_commands.createGitRepoAndRemotes(repo_name, authenticated_remote_url);
  sh.run(create_statement);
}
function pullLatest(info){
	var repo_name = info.repository.name,
		branch_name = info.ref.split('/')[2], // `ref: "refs/heads/<branchname>` => `branchname`
		delete_branch;

	if (!fs.existsSync('./repositories/' + repo_name)){
		createDirGitInit(info);
	}

	// Download latest data
	var fetch_statement = sh_commands.fetchLatest(repo_name);
	sh.run(fetch_statement);

	// If it's deleted, delete it!
	if (info.deleted) {
		delete_branch = sh_commands.deleteBranch(repo_name, branch_name);
		sh.run(delete_branch);
	}

	// Update all branches
	var track_all_branches = sh_commands.trackAllBranches(repo_name);
	sh.run(track_all_branches);

	// Put the working tree back on to master
	var checkout_master = sh_commands.checkoutMaster(repo_name);
	sh.run(checkout_master);
}
function checkForDeployMsg(last_commit){
	var commit_msg = last_commit.message,
	    cp_deploy_regx   = new RegExp(config.s3.hard_deploy.trigger),
	    sync_deploy_regx = new RegExp(config.s3.sync_deploy_trigger);

	if (config.s3.hard_deploy.enabled && cp_deploy_regx.exec(commit_msg)) return 'cp';
	if (sync_deploy_regx.exec(commit_msg)) return 'sync';
	return false;
}
function deployToS3(deploy_type, most_recent_commit){
	var repo_name   = info.repository.name,
			local_path  = most_recent_commit.split('::')[1], // `published::output` -> `output` or `published::` -> `''`
	    remote_path = config.s3.path || '';

	// If a local path was specified, put a slash in front of it
	if (local_path) local_path = '/' + local_path;

	var deploy_statement = sh_commands.deploy(deploy_type, repo_name, config.s3.bucket_name, local_path, remote_path, config.s3.exclude);
	var deploy_result = sh.exec(deploy_statement);
	// Log deployment result
	console.log(deploy_result.stdout);
}

hookshot(function(info){
	// Is this request coming from the specified GitHub Account?
	var is_account_verified = verifyAccount(info.repository.owner.name);
	// Is there a deploy message present?
	var most_recent_commit,
			deploy_status;
	// The last commit in the array is the most recent
	// But `info.commits` will be an empty array if you pushed a new branch with no commits
	if (info.commits.length) {
		most_recent_commit  = info.commits[info.commits.length - 1];
		deploy_status       = checkForDeployMsg(most_recent_commit);
	}
	// Is this coming from the whitelisted GitHub account?
	if (is_account_verified){
		pullLatest(info);

		// Are we deploying? Has that option been enabled and does the commit have the appropriate message?
		if (config.s3.enabled && deploy_status){
			verifyCommitter(most_recent_commit, function(committer_approved){

				// Does the committer have deploy? privileges?
				if (committer_approved) {
					deployToS3(deploy_status, most_recent_commit);
				} else {
					console.log('Unapproved committer attempted deployment.'.red)
				}

			});
		}
	}
}).listen(config.github_listener.port);

console.log('Listening on port... ' + config.github_listener.port);	<|endoftext|>


<commit_before>function compareForSortOnBestScore(a, b) {
	if(a['autocomplete_score'] < b['autocomplete_score']){
		return -1;
	} else if(a['autocomplete_score'] > b['autocomplete_score']) {
		return 1;
	} else if (a['autocomplete_rawdata_on'] < b['autocomplete_rawdata_on']) {
		return -1;
	} else if (a['autocomplete_rawdata_on'] > b['autocomplete_rawdata_on']) {
		return 1;
	}
	return 0;
}

Array.prototype.sortOnBestScore = function() {
	this.sort(compareForSortOnBestScore);
}

Array.prototype.shuffle = function() {
	var i = this.length, j, temp;
	if (i == 0) return this;
	while (--i) {
		j = Math.floor(Math.random() * (i + 1));
		temp = this[i];
		this[i] = this[j];
		this[j] = temp;
	}
	return this;
}

function partialSortHelper(tab, num_elts, start, end) {
	if (start >= end) {
		return;
	}

	var pivot_pos = start;
	var pivot_value = tab[end -1];
	for (var pos = start ; pos < end -1 ; ++pos) {
		var at_pos = tab[pos];
		if (compareForSortOnBestScore(at_pos, pivot_value) == -1) {
			tab[pos] = tab[pivot_pos];
			tab[pivot_pos] = at_pos;
			++pivot_pos;
		}
	}
	tab[end -1] = tab[pivot_pos];
	tab[pivot_pos] = pivot_value;

	partialSortHelper(tab, num_elts, start, pivot_pos);
	if (pivot_pos +1 < num_elts) {
		partialSortHelper(tab, num_elts, pivot_pos +1, end);
	}
}

function partialSort(tab, num_elts) {
	/** /!\ tab is modified,                                  *
	 *      elements might disappear(size might be different) *
	 *      ordering of elements might be changed too         */
	tab.shuffle();
	partialSortHelper(tab, num_elts, 0, tab.length);
	return tab.slice(0, num_elts);
}

function toSafeHtml(text) {
	return $("<a/>").text(text).html();
}

function AutocompleteItem($input, available_elts) {
	var self = this;

	// jQuery element corresponding to a text input
	// This text input will benefit from autocompletion feature
	self.$input = $input;

	// A list of available elements that can be displayed to the end-user
	// Each element should have the fields:
	// - autocomplete_id
	// - autocomplete_rawdata_on: text to display during completion
	self.available_elts = available_elts;

	// Update available elements available
	self.updateList = function(available_elts) {
		self.available_elts = available_elts;
	};

	// Callback called when selecting an element from the autocomplete-list
	// Paramaters are: function($input, selected_elt)
	// - $input: the input linked to this object ie. self.$input
	// - selected_elt: an element from self.available_elts (self.available_elts[i])
	self.onSelectCallback = undefined;
	self.setOnSelectCallback = function(callback) {
		self.onSelectCallback = callback;
	};

	// Callback called during the creation of the autocomplete-list
	// Parameters are: function($input, elt)
	// - $input: the input linked to this object ie. self.$input
	// - elt: an element from self.available_elts (self.available_elts[i])
	// Return:
	// - false: the element can be added to the list
	self.onFilterChoicesCallback = undefined;
	self.setOnFilterChoicesCallback = function(callback) {
		self.onFilterChoicesCallback = callback;
	};

	// Boolean to specify whether or not the field should be freed
	// after each selection
	self.automaticallyEraseValue = true;
	self.setAutomaticallyEraseValue = function(automaticallyEraseValue) {
		self.automaticallyEraseValue = automaticallyEraseValue;
	};

	// THe maximum number of results expected
	self.numMaxResults = -1;
	self.setNumMaxResults = function(numMaxResults) {
		self.numMaxResults = numMaxResults;
	};

	self.showForTooMany = 0;
	self.setShowForTooMany = function(showForTooMany) {
		self.showForTooMany = showForTooMany;
	};

	// Behaviour on 'key up' event
	// Update autocomplete list
	self.reactKeyUp = function(event) {
		// Refresh the content of the autocomplete list
		// $(this) == self.jquery_input

		// Get autocomplete list or create it if not displayed
		var $input_parent = $(this).parent();
		var $autocomplete_list = $input_parent.find(".autocomplete-list");
		if ($autocomplete_list.length == 0) {
			$autocomplete_list = $("<ul/>");
			$autocomplete_list.addClass("autocomplete-list");
			$input_parent.append($autocomplete_list);
		}

		// Show the autocomplete list at the right place
		var position_left = $(this).position()['left'];
		var position_top = $(this).position()['top'] + $(this).height();
		$autocomplete_list.css('left', position_left + 'px');
		$autocomplete_list.css('top', position_top + 'px');

		// Get already selected elements position
		var $selected_elt = $autocomplete_list.find('.autocomplete-list-selected').first();
		var selected_elt_id = -1;
		if ($selected_elt.length == 1) {
			selected_elt_id = parseInt($selected_elt.attr('data-autocomplete-id'));
		}
		if (selected_elt_id != -1 && event.keyCode == 13) { // Enter
			self.confirmChoice(selected_elt_id);
			if (self.automaticallyEraseValue) {
				$(this).val("");
			}
			$autocomplete_list.remove();
			event.preventDefault();
			return;
		}
		else if (event.keyCode == 38 || event.keyCode == 40) { // Up or Down
			var $autocomplete_elts = $autocomplete_list.children();
			var current_index = 0;
			for (current_index=0 ; current_index != $autocomplete_elts.length ; current_index++) {
				if ($($autocomplete_elts[current_index]).hasClass('autocomplete-list-selected')) {
					break;
				}
			}
			if ($autocomplete_elts.length > 0) {
				if (current_index == $autocomplete_elts.length) {
					$($autocomplete_elts[0]).addClass('autocomplete-list-selected');
				} else if (event.keyCode == 38) {
					if (current_index > 0) {
						$($autocomplete_elts[current_index]).removeClass('autocomplete-list-selected');
						$($autocomplete_elts[current_index -1]).addClass('autocomplete-list-selected');
					}
				} else if (event.keyCode == 40) {
					if (current_index < $autocomplete_elts.length -1) {
						$($autocomplete_elts[current_index]).removeClass('autocomplete-list-selected');
						$($autocomplete_elts[current_index +1]).addClass('autocomplete-list-selected');
					}
				}
				event.preventDefault();
				return;
			}
		} else if (event.keyCode == 27) {
			$autocomplete_list.remove();
		}

		// Create autocomplete list
		var elts_to_display = self.computeChoices($(this).val());

		// Display elements
		$autocomplete_list.empty();
		for (var i=0 ; i != elts_to_display.length ; i++) {
			var $autocomplete_elt = $("<li/>");
			$autocomplete_elt.attr('data-autocomplete-id', elts_to_display[i]['autocomplete_id']);
			if (elts_to_display[i]['autocomplete_id'] == selected_elt_id) {
				$autocomplete_elt.addClass('autocomplete-list-selected');
			}
			$autocomplete_elt.click(function() {
				self.confirmChoice($(this).attr("data-autocomplete-id"));
				if (self.automaticallyEraseValue) {
					$(this).parent().parent().find("input").val("");
				}
				$(this).parent().remove(); //remove list
			});
			$autocomplete_elt.html(elts_to_display[i]['autocomplete_display']);
			$autocomplete_elt.mouseover(function() {
				var $autocomplete_list = $(this).parent();
				var $autocomplete_choices = $autocomplete_list.children();
				for (var i = 0 ; i != $autocomplete_choices.length ; i++) {
					$($autocomplete_choices[i]).removeClass('autocomplete-list-selected');
				}
				$(this).addClass('autocomplete-list-selected');
			});
			$autocomplete_list.append($autocomplete_elt);
		}
		if (elts_to_display.length == 0) {
			$autocomplete_list.remove();
		}
	};

	// Compute the list of available choices based on the query
	self.computeChoices = function(query) {
		var elts_to_display = new Array();
		for (var i=0 ; i!=self.available_elts.length ; i++) {
			if (self.onFilterChoicesCallback
					&& self.onFilterChoicesCallback(self.$input, self.available_elts[i])) {
				continue;
			}
			var new_elt = self.computePriority(query, i);
			if (new_elt) {
				elts_to_display.push(new_elt);
				if (self.showForTooMany > 0 && elts_to_display.length > self.showForTooMany) {
					return [];
				}
			}
		}

		if (self.numMaxResults > 0) {
			return partialSort(elts_to_display, self.numMaxResults);
		}

		elts_to_display.sortOnBestScore();
		return elts_to_display;
	};

	// Compute the score for element i
	// Score is stored into the element itself
	self.computePriority = function(query, i) {
		var elt = self.available_elts[i];
		var elt_text = elt['autocomplete_rawdata_on'];
		var elt_text_lower = elt_text.toLowerCase();
		var query_lower = query.toLowerCase();

		var best_origin = -1;
		var best_score = -1;
		for (var i=0 ; i!=elt_text_lower.length ; i++) { // Look for a string starting from this element
			if (query_lower.length != 0 && elt_text_lower[i] != query_lower[0]) {
				continue;
			}
			var padding_pos = 0;
			var query_pos = 0;
			for (query_pos = 0 ; i+padding_pos != elt_text_lower.length && query_pos != query_lower.length ; padding_pos++) {
				if (elt_text_lower[i+padding_pos] == query_lower[query_pos]) {
					query_pos++;
				}
			}

			// Is there a match?
			// If so, is it better than current one?
			if (query_pos == query_lower.length) {//match
				if (best_score == -1 || best_score > padding_pos-query_pos) {
					best_origin = i;
					best_score = padding_pos-query_pos;
					if (best_score == 0) {
						break;
					}
				}
			}
		}

		// Highlight match characteristics
		if (best_score != -1) {
			var new_elt = elt;
			new_elt["autocomplete_score"] = best_score;
			if (query.length == 0) {
				new_elt['autocomplete_display'] = toSafeHtml(elt_text);
			} else {
				new_elt['autocomplete_display'] = "";
				var i = 0;
				var query_pos = 0;
				for ( ; i != elt_text_lower.length ; i++) {
					if (i >= best_origin && query_pos != query_lower.length && elt_text_lower[i] == query_lower[query_pos]) {
						new_elt['autocomplete_display'] += "<b>" + toSafeHtml(elt_text[i]) + "</b>";
						query_pos++;
					}
					else
					{
						new_elt['autocomplete_display'] += toSafeHtml(elt_text[i]);
					}
				}
			}
			return new_elt;
		}
		return undefined;
	};

	self.confirmChoice = function(selected_elt_id) {
		if (! self.onSelectCallback) {
			console.warn("No callback has been specified for onSelect");
			return;
		}

		var i = 0;
		for (i = 0 ; i != self.available_elts.length ; i++) {
			if (self.available_elts[i]['autocomplete_id'] == selected_elt_id) {
				break;
			}
		}
		if (i != self.available_elts.length) {
			var choice = self.available_elts[i];
			self.onSelectCallback(self.$input, self.available_elts[i]);
		}
	};

	// Check whether or not the autocompletion list
	// should still be visible
	// Hide the autocomplete-list if the click is not on:
	// - the input
	// - the autocomplete-list or one of its children
	self.clickSomewhere = function(event) {
		var target = event.target;
		if (target == self.$input[0]) {
			return;
		}
		var $expected_parent = self.$input.parent().find(".autocomplete-list");
		if (target == $expected_parent[0]) {
			return;
		}
		var $parents = $(target).parents();
		for (var i = 0 ; i != $parents.length ; i++) {
			if ($(this) == $($parents[i])) {
				return;
			}
		}
		self.$input.parent().find(".autocomplete-list").remove();
	};

	// Add autocompletion trigger to the input field
	{
		self.$input.keyup(self.reactKeyUp);
		self.$input.parent().css('position', 'relative');
		$(document).click(self.clickSomewhere);
	}
}

<commit_msg>Autocompletion list appear at position outerHeight<commit_after>function compareForSortOnBestScore(a, b) {
	if(a['autocomplete_score'] < b['autocomplete_score']){
		return -1;
	} else if(a['autocomplete_score'] > b['autocomplete_score']) {
		return 1;
	} else if (a['autocomplete_rawdata_on'] < b['autocomplete_rawdata_on']) {
		return -1;
	} else if (a['autocomplete_rawdata_on'] > b['autocomplete_rawdata_on']) {
		return 1;
	}
	return 0;
}

Array.prototype.sortOnBestScore = function() {
	this.sort(compareForSortOnBestScore);
}

Array.prototype.shuffle = function() {
	var i = this.length, j, temp;
	if (i == 0) return this;
	while (--i) {
		j = Math.floor(Math.random() * (i + 1));
		temp = this[i];
		this[i] = this[j];
		this[j] = temp;
	}
	return this;
}

function partialSortHelper(tab, num_elts, start, end) {
	if (start >= end) {
		return;
	}

	var pivot_pos = start;
	var pivot_value = tab[end -1];
	for (var pos = start ; pos < end -1 ; ++pos) {
		var at_pos = tab[pos];
		if (compareForSortOnBestScore(at_pos, pivot_value) == -1) {
			tab[pos] = tab[pivot_pos];
			tab[pivot_pos] = at_pos;
			++pivot_pos;
		}
	}
	tab[end -1] = tab[pivot_pos];
	tab[pivot_pos] = pivot_value;

	partialSortHelper(tab, num_elts, start, pivot_pos);
	if (pivot_pos +1 < num_elts) {
		partialSortHelper(tab, num_elts, pivot_pos +1, end);
	}
}

function partialSort(tab, num_elts) {
	/** /!\ tab is modified,                                  *
	 *      elements might disappear(size might be different) *
	 *      ordering of elements might be changed too         */
	tab.shuffle();
	partialSortHelper(tab, num_elts, 0, tab.length);
	return tab.slice(0, num_elts);
}

function toSafeHtml(text) {
	return $("<a/>").text(text).html();
}

function AutocompleteItem($input, available_elts) {
	var self = this;

	// jQuery element corresponding to a text input
	// This text input will benefit from autocompletion feature
	self.$input = $input;

	// A list of available elements that can be displayed to the end-user
	// Each element should have the fields:
	// - autocomplete_id
	// - autocomplete_rawdata_on: text to display during completion
	self.available_elts = available_elts;

	// Update available elements available
	self.updateList = function(available_elts) {
		self.available_elts = available_elts;
	};

	// Callback called when selecting an element from the autocomplete-list
	// Paramaters are: function($input, selected_elt)
	// - $input: the input linked to this object ie. self.$input
	// - selected_elt: an element from self.available_elts (self.available_elts[i])
	self.onSelectCallback = undefined;
	self.setOnSelectCallback = function(callback) {
		self.onSelectCallback = callback;
	};

	// Callback called during the creation of the autocomplete-list
	// Parameters are: function($input, elt)
	// - $input: the input linked to this object ie. self.$input
	// - elt: an element from self.available_elts (self.available_elts[i])
	// Return:
	// - false: the element can be added to the list
	self.onFilterChoicesCallback = undefined;
	self.setOnFilterChoicesCallback = function(callback) {
		self.onFilterChoicesCallback = callback;
	};

	// Boolean to specify whether or not the field should be freed
	// after each selection
	self.automaticallyEraseValue = true;
	self.setAutomaticallyEraseValue = function(automaticallyEraseValue) {
		self.automaticallyEraseValue = automaticallyEraseValue;
	};

	// THe maximum number of results expected
	self.numMaxResults = -1;
	self.setNumMaxResults = function(numMaxResults) {
		self.numMaxResults = numMaxResults;
	};

	self.showForTooMany = 0;
	self.setShowForTooMany = function(showForTooMany) {
		self.showForTooMany = showForTooMany;
	};

	// Behaviour on 'key up' event
	// Update autocomplete list
	self.reactKeyUp = function(event) {
		// Refresh the content of the autocomplete list
		// $(this) == self.jquery_input

		// Get autocomplete list or create it if not displayed
		var $input_parent = $(this).parent();
		var $autocomplete_list = $input_parent.find(".autocomplete-list");
		if ($autocomplete_list.length == 0) {
			$autocomplete_list = $("<ul/>");
			$autocomplete_list.addClass("autocomplete-list");
			$input_parent.append($autocomplete_list);
		}

		// Show the autocomplete list at the right place
		var position_left = $(this).position()['left'];
		var position_top = $(this).position()['top'] + $(this).outerHeight();
		$autocomplete_list.css('left', position_left + 'px');
		$autocomplete_list.css('top', position_top + 'px');

		// Get already selected elements position
		var $selected_elt = $autocomplete_list.find('.autocomplete-list-selected').first();
		var selected_elt_id = -1;
		if ($selected_elt.length == 1) {
			selected_elt_id = parseInt($selected_elt.attr('data-autocomplete-id'));
		}
		if (selected_elt_id != -1 && event.keyCode == 13) { // Enter
			self.confirmChoice(selected_elt_id);
			if (self.automaticallyEraseValue) {
				$(this).val("");
			}
			$autocomplete_list.remove();
			event.preventDefault();
			return;
		}
		else if (event.keyCode == 38 || event.keyCode == 40) { // Up or Down
			var $autocomplete_elts = $autocomplete_list.children();
			var current_index = 0;
			for (current_index=0 ; current_index != $autocomplete_elts.length ; current_index++) {
				if ($($autocomplete_elts[current_index]).hasClass('autocomplete-list-selected')) {
					break;
				}
			}
			if ($autocomplete_elts.length > 0) {
				if (current_index == $autocomplete_elts.length) {
					$($autocomplete_elts[0]).addClass('autocomplete-list-selected');
				} else if (event.keyCode == 38) {
					if (current_index > 0) {
						$($autocomplete_elts[current_index]).removeClass('autocomplete-list-selected');
						$($autocomplete_elts[current_index -1]).addClass('autocomplete-list-selected');
					}
				} else if (event.keyCode == 40) {
					if (current_index < $autocomplete_elts.length -1) {
						$($autocomplete_elts[current_index]).removeClass('autocomplete-list-selected');
						$($autocomplete_elts[current_index +1]).addClass('autocomplete-list-selected');
					}
				}
				event.preventDefault();
				return;
			}
		} else if (event.keyCode == 27) {
			$autocomplete_list.remove();
		}

		// Create autocomplete list
		var elts_to_display = self.computeChoices($(this).val());

		// Display elements
		$autocomplete_list.empty();
		for (var i=0 ; i != elts_to_display.length ; i++) {
			var $autocomplete_elt = $("<li/>");
			$autocomplete_elt.attr('data-autocomplete-id', elts_to_display[i]['autocomplete_id']);
			if (elts_to_display[i]['autocomplete_id'] == selected_elt_id) {
				$autocomplete_elt.addClass('autocomplete-list-selected');
			}
			$autocomplete_elt.click(function() {
				self.confirmChoice($(this).attr("data-autocomplete-id"));
				if (self.automaticallyEraseValue) {
					$(this).parent().parent().find("input").val("");
				}
				$(this).parent().remove(); //remove list
			});
			$autocomplete_elt.html(elts_to_display[i]['autocomplete_display']);
			$autocomplete_elt.mouseover(function() {
				var $autocomplete_list = $(this).parent();
				var $autocomplete_choices = $autocomplete_list.children();
				for (var i = 0 ; i != $autocomplete_choices.length ; i++) {
					$($autocomplete_choices[i]).removeClass('autocomplete-list-selected');
				}
				$(this).addClass('autocomplete-list-selected');
			});
			$autocomplete_list.append($autocomplete_elt);
		}
		if (elts_to_display.length == 0) {
			$autocomplete_list.remove();
		}
	};

	// Compute the list of available choices based on the query
	self.computeChoices = function(query) {
		var elts_to_display = new Array();
		for (var i=0 ; i!=self.available_elts.length ; i++) {
			if (self.onFilterChoicesCallback
					&& self.onFilterChoicesCallback(self.$input, self.available_elts[i])) {
				continue;
			}
			var new_elt = self.computePriority(query, i);
			if (new_elt) {
				elts_to_display.push(new_elt);
				if (self.showForTooMany > 0 && elts_to_display.length > self.showForTooMany) {
					return [];
				}
			}
		}

		if (self.numMaxResults > 0) {
			return partialSort(elts_to_display, self.numMaxResults);
		}

		elts_to_display.sortOnBestScore();
		return elts_to_display;
	};

	// Compute the score for element i
	// Score is stored into the element itself
	self.computePriority = function(query, i) {
		var elt = self.available_elts[i];
		var elt_text = elt['autocomplete_rawdata_on'];
		var elt_text_lower = elt_text.toLowerCase();
		var query_lower = query.toLowerCase();

		var best_origin = -1;
		var best_score = -1;
		for (var i=0 ; i!=elt_text_lower.length ; i++) { // Look for a string starting from this element
			if (query_lower.length != 0 && elt_text_lower[i] != query_lower[0]) {
				continue;
			}
			var padding_pos = 0;
			var query_pos = 0;
			for (query_pos = 0 ; i+padding_pos != elt_text_lower.length && query_pos != query_lower.length ; padding_pos++) {
				if (elt_text_lower[i+padding_pos] == query_lower[query_pos]) {
					query_pos++;
				}
			}

			// Is there a match?
			// If so, is it better than current one?
			if (query_pos == query_lower.length) {//match
				if (best_score == -1 || best_score > padding_pos-query_pos) {
					best_origin = i;
					best_score = padding_pos-query_pos;
					if (best_score == 0) {
						break;
					}
				}
			}
		}

		// Highlight match characteristics
		if (best_score != -1) {
			var new_elt = elt;
			new_elt["autocomplete_score"] = best_score;
			if (query.length == 0) {
				new_elt['autocomplete_display'] = toSafeHtml(elt_text);
			} else {
				new_elt['autocomplete_display'] = "";
				var i = 0;
				var query_pos = 0;
				for ( ; i != elt_text_lower.length ; i++) {
					if (i >= best_origin && query_pos != query_lower.length && elt_text_lower[i] == query_lower[query_pos]) {
						new_elt['autocomplete_display'] += "<b>" + toSafeHtml(elt_text[i]) + "</b>";
						query_pos++;
					}
					else
					{
						new_elt['autocomplete_display'] += toSafeHtml(elt_text[i]);
					}
				}
			}
			return new_elt;
		}
		return undefined;
	};

	self.confirmChoice = function(selected_elt_id) {
		if (! self.onSelectCallback) {
			console.warn("No callback has been specified for onSelect");
			return;
		}

		var i = 0;
		for (i = 0 ; i != self.available_elts.length ; i++) {
			if (self.available_elts[i]['autocomplete_id'] == selected_elt_id) {
				break;
			}
		}
		if (i != self.available_elts.length) {
			var choice = self.available_elts[i];
			self.onSelectCallback(self.$input, self.available_elts[i]);
		}
	};

	// Check whether or not the autocompletion list
	// should still be visible
	// Hide the autocomplete-list if the click is not on:
	// - the input
	// - the autocomplete-list or one of its children
	self.clickSomewhere = function(event) {
		var target = event.target;
		if (target == self.$input[0]) {
			return;
		}
		var $expected_parent = self.$input.parent().find(".autocomplete-list");
		if (target == $expected_parent[0]) {
			return;
		}
		var $parents = $(target).parents();
		for (var i = 0 ; i != $parents.length ; i++) {
			if ($(this) == $($parents[i])) {
				return;
			}
		}
		self.$input.parent().find(".autocomplete-list").remove();
	};

	// Add autocompletion trigger to the input field
	{
		self.$input.keyup(self.reactKeyUp);
		self.$input.parent().css('position', 'relative');
		$(document).click(self.clickSomewhere);
	}
}

<|endoftext|>


<commit_before>/*jshint esversion: 6 */

var Config = require('./config.json');
//setup dev env
var DATABOX_DEV = process.env.DATABOX_DEV;
if(DATABOX_DEV == 1) {

	Config.registryUrl =  Config.registryUrl_dev;
  	Config.storeUrl = Config.storeUrl_dev;
}

var DATABOX_SDK = process.env.DATABOX_SDK;
if(DATABOX_SDK == 1) {

	Config.registryUrl =  Config.registryUrl_sdk;
  	Config.storeUrl = Config.storeUrl_sdk;
}

var http = require('http');
var https = require('https');
var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var databoxRequestPromise = require('./lib/databox-request-promise.js');
var databoxAgent = require('./lib/databox-https-agent.js');
var io = require('socket.io');
var url = require('url');

var app = express();

module.exports = {
	proxies: {},
	app: app,
	launch: function (port, conman, httpsHelper) {

		var server = http.createServer(app);
		var installingApps = {};
		io = io(server, {});

		if(DATABOX_DEV == 1) {
			this.proxies.store = "http://" + Config.localAppStoreName+ ":8181";
		} else {
			this.proxies.store = Config.storeUrl;
		}

		app.enable('trust proxy');
		app.set('views', 'src/www');
		app.set('view engine', 'pug');
		app.use(express.static('src/www'));

		app.use((req, res, next) => {
			var firstPart = req.path.split('/')[1];
			if (firstPart in this.proxies) {
				var replacement = this.proxies[firstPart];
				var proxyURL;
				if (replacement.indexOf('://') != -1) {
					var parts = url.parse(replacement);
					parts.pathname = req.baseUrl + req.path.substring(firstPart.length + 1);
					parts.query = req.query;
					proxyURL = url.format(parts);
				}
				else {
					proxyURL = url.format({
						protocol: 'https',
						host: replacement,
						pathname: req.baseUrl + req.path.substring(firstPart.length + 1),
						query: req.query
					});
				}

				console.log("[Proxy] " + req.method + ": " + req.url + " => " + proxyURL);
				databoxRequestPromise({uri:proxyURL})
				.then((resolvedRequest)=>{

					return req.pipe(resolvedRequest)
							.on('error', (e) => {
								console.log('[Proxy] ERROR: ' + req.url + " " + e.message);
							})
							.pipe(res)
							.on('error', (e) => {
								console.log('[Proxy] ERROR: ' + req.url + " " + e.message);
							})
							.on('end',()=>{
								next();
							});
				});

			} else {
				next();
			}
		});

		// Needs to be after the proxy
		app.use(bodyParser.urlencoded({extended: false}));

		app.get('/', (req, res) => {
			res.render('index');
		});
		app.get('/install/:appname', (req, res) => {
			res.render('install', {appname: req.params.appname})
		});

		app.get('/ui/:containerName', (req, res) => {
			let containerName = req.params.containerName;
			conman.listContainers()
				.then((containers) => {
					return new Promise((resolve,reject)=>{
						for (let container of containers) {
							let name = container.Names[0].substr(1);
							if (name == containerName ){
								resolve({
									name: name,
									container_id: container.Id,
									type: container.Labels['databox.type'] === undefined ? 'app' : container.Labels['databox.type'],
									status: container.State
								});
								return;
							}
						}
						reject("[Error] " + containerName + " not found.");
					});
				})
				.then((containerInfo)=>{
					var path = '/ui';
					if(containerInfo.type == 'store') {
						path = '/cat';
					}
					res.render('ui', {appname: containerInfo.name, path: path });
				})
				.catch((error)=>{
					res.render('ui', {error: error});
				});

		});

		app.get('/list-apps', (req, res) => {
			let names = [];
			let result = [];

			conman.listContainers()
				.then((containers) => {
					for (let container of containers) {
						let name = container.Names[0].substr(1);
						names.push(name);
						result.push({
							name: name,
							container_id: container.Id,
							type: container.Labels['databox.type'] === undefined ? 'app' : container.Labels['databox.type'],
							status: container.State
						});
					}

					for (let installingApp in installingApps) {
						if (names.indexOf(installingApp) === -1) {
							names.push(installingApp);
							result.push({
								name: installingApp,
								type: installingApps[installingApp],
								status: 'installing'
							});
						}
					}

					let options = {'url': '', 'method': 'GET'};
					if(DATABOX_DEV == 1) {
						options.url = "http://" + Config.localAppStoreName+ ":8181" + '/app/list';
					} else {
						options.url = Config.storeUrl + '/app/list';
					}
					return new Promise((resolve,reject)=>{
						request(options, (error, response, body) => {
							if (error) {
								console.log("Error: " + options.url);
								reject(error);
								return;
							}

							resolve(JSON.parse(body).apps);
						});

					});
				})
				.then((apps)=>{
					for(let app of apps) {
						if (names.indexOf(app.manifest.name) === -1) {
							names.push(app.manifest.name);
							result.push({
								name: app.manifest.name,
								type: app.manifest['databox-type'] === undefined ? 'app' : app.manifest['databox-type'],
								status: 'uninstalled',
								author: app.manifest.author
							});
						}
					}

					res.json(result);
				})
				.catch((err)=>{
					console.log("[Error] ",err);
					res.json(err);
				});

		});

		app.post('/install', (req, res) => {
			var sla = JSON.parse(req.body.sla);
			installingApps[sla.name] = sla['databox-type'] === undefined ? 'app' : sla['databox-type'];

			io.emit('docker-create', sla.name);
			conman.launchContainer(sla)
				.then((containers) => {
					console.log('[' + sla.name + '] Installed');
					for (var container of containers) {

						delete installingApps[container.name];
						this.proxies[container.name] = container.name + ':' + container.port;
					}

					res.json(containers);
				})
				.then(() => {
					return conman.saveSLA(sla);
				});
		});

		app.post('/restart', (req, res) => {
			//console.log("Restarting " + req.body.id);
			conman.getContainer(req.body.id)
				.then((container) => {
					return conman.stopContainer(container);
				})
				.then((container) => {
					return conman.startContainer(container);
				})
				.then((container) => {
					console.log('[' + container.name + '] Restarted');
					this.proxies[container.name] = container.name + ':' + container.port;
				})
				.catch((err)=> {
					console.log(err);
					res.json(err);
				});
		});

		app.post('/uninstall', (req, res) => {
			//console.log("Uninstalling " + req.body.id);
			conman.getContainer(req.body.id)
				.then((container)=> {
					return conman.stopContainer(container);
				})
				.then((container)=> {
					return conman.removeContainer(container);
				})
				.then((info)=> {
					var name = info.Name;
					if (info.Name.startsWith('/')) {
						name = info.Name.substring(1);
					}
					console.log('[' + name + '] Uninstalled');
					delete this.proxies[name];
					res.json(info);
				})
				.catch((err)=> {
					console.log(err);
					res.json(err)
				});
		});

		io.on('connection', (socket) => {
			var emitter = conman.getDockerEmitter();

			emitter.on("connect", () => {
				socket.emit('docker-connect');
			});
			emitter.on("disconnect", () => {
				socket.emit('docker-disconnect');
			});
			emitter.on("_message", (message) => {
				socket.emit('docker-_message', message);
			});
			emitter.on("create", (message) => {
				socket.emit('docker-create', message);
			});
			emitter.on("start", (message) => {
				socket.emit('docker-star', message);
			});
			emitter.on("start", (message) => {
				socket.emit('docker-stop', message);
			});
			emitter.on("die", (message) => {
				socket.emit('docker-die', message);
			});
			emitter.on("destroy", (message) => {
				socket.emit('docker-destroy', message);
			});
			emitter.start();

		});

		server.listen(port);
	}
};
<commit_msg>Fixes & tidying<commit_after>/*jshint esversion: 6 */

const Config = require('./config.json');
//setup dev env
const DATABOX_DEV = process.env.DATABOX_DEV;
if (DATABOX_DEV == 1) {

	Config.registryUrl = Config.registryUrl_dev;
	Config.storeUrl = Config.storeUrl_dev;
}

const DATABOX_SDK = process.env.DATABOX_SDK;
if (DATABOX_SDK == 1) {

	Config.registryUrl = Config.registryUrl_sdk;
	Config.storeUrl = Config.storeUrl_sdk;
}

const http = require('http');
const https = require('https');
const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const databoxRequestPromise = require('./lib/databox-request-promise.js');
const databoxAgent = require('./lib/databox-https-agent.js');
let io = require('socket.io');
const url = require('url');

const app = express();

module.exports = {
	proxies: {},
	app: app,
	launch: function (port, conman, httpsHelper) {

		const server = http.createServer(app);
		const installingApps = {};
		io = io(server, {});

		if (DATABOX_DEV == 1) {
			this.proxies.store = "http://" + Config.localAppStoreName + ":8181";
		} else {
			this.proxies.store = Config.storeUrl;
		}

		app.enable('trust proxy');
		app.set('views', 'src/www');
		app.set('view engine', 'pug');
		app.use(express.static('src/www'));

		app.use((req, res, next) => {
			const firstPart = req.path.split('/')[1];
			if (firstPart in this.proxies) {
				const replacement = this.proxies[firstPart];
				let proxyURL;
				if (replacement.indexOf('://') != -1) {
					const parts = url.parse(replacement);
					parts.pathname = req.baseUrl + req.path.substring(firstPart.length + 1);
					parts.query = req.query;
					proxyURL = url.format(parts);
				}
				else {
					proxyURL = url.format({
						protocol: 'https',
						host: replacement,
						pathname: req.baseUrl + req.path.substring(firstPart.length + 1),
						query: req.query
					});
				}

				console.log("[Proxy] " + req.method + ": " + req.url + " => " + proxyURL);
				databoxRequestPromise({uri: proxyURL})
					.then((resolvedRequest) => {

						return req.pipe(resolvedRequest)
							.on('error', (e) => {
								console.log('[Proxy] ERROR: ' + req.url + " " + e.message);
							})
							.pipe(res)
							.on('error', (e) => {
								console.log('[Proxy] ERROR: ' + req.url + " " + e.message);
							})
							.on('end', () => {
								next();
							});
					});

			} else {
				next();
			}
		});

		// Needs to be after the proxy
		app.use(bodyParser.urlencoded({extended: false}));

		app.get('/', (req, res) => {
			res.render('index');
		});
		app.get('/install/:appname', (req, res) => {
			res.render('install', {appname: req.params.appname})
		});

		app.get('/ui/:containerName', (req, res) => {
			let containerName = req.params.containerName;
			conman.listContainers()
				.then((containers) => {
					return new Promise((resolve, reject) => {
						for (let container of containers) {
							let name = container.Names[0].substr(1);
							if (name == containerName) {
								resolve({
									name: name,
									container_id: container.Id,
									type: container.Labels['databox.type'] === undefined ? 'app' : container.Labels['databox.type'],
									status: container.State
								});
								return;
							}
						}
						reject("[Error] " + containerName + " not found.");
					});
				})
				.then((containerInfo) => {
					let path = '/ui';
					if (containerInfo.type == 'store') {
						path = '/cat';
					}
					res.render('ui', {appname: containerInfo.name, path: path});
				})
				.catch((error) => {
					res.render('ui', {error: error});
				});

		});

		app.get('/list-apps', (req, res) => {
			let names = [];
			let result = [];

			conman.listContainers()
				.then((containers) => {
					for (let container of containers) {
						let name = container.Names[0].substr(1);
						names.push(name);
						result.push({
							name: name,
							container_id: container.Id,
							type: container.Labels['databox.type'] === undefined ? 'app' : container.Labels['databox.type'],
							status: container.State
						});
					}

					for (let installingApp in installingApps) {
						if (names.indexOf(installingApp) === -1) {
							names.push(installingApp);
							result.push({
								name: installingApp,
								type: installingApps[installingApp],
								status: 'installing'
							});
						}
					}

					let options = {'url': '', 'method': 'GET'};
					if (DATABOX_DEV == 1) {
						options.url = "http://" + Config.localAppStoreName + ":8181" + '/app/list';
					} else {
						options.url = Config.storeUrl + '/app/list';
					}
					return new Promise((resolve, reject) => {
						request(options, (error, response, body) => {
							if (error) {
								console.log("Error: " + options.url);
								reject(error);
								return;
							}

							resolve(JSON.parse(body).apps);
						});

					});
				})
				.then((apps) => {
					for (let app of apps) {
						if (names.indexOf(app.manifest.name) === -1) {
							names.push(app.manifest.name);
							result.push({
								name: app.manifest.name,
								type: app.manifest['databox-type'] === undefined ? 'app' : app.manifest['databox-type'],
								status: 'uninstalled',
								author: app.manifest.author
							});
						}
					}

					res.json(result);
				})
				.catch((err) => {
					console.log("[Error] ", err);
					res.json(err);
				});

		});

		app.post('/install', (req, res) => {
			const sla = JSON.parse(req.body.sla);
			installingApps[sla.name] = sla['databox-type'] === undefined ? 'app' : sla['databox-type'];

			io.emit('docker-create', sla.name);
			conman.launchContainer(sla)
				.then((containers) => {
					console.log('[' + sla.name + '] Installed');
					for (const container of containers) {

						delete installingApps[sla.name];
						this.proxies[container.name] = container.name + ':' + container.port;
					}

					res.json(containers);
				})
				.then(() => {
					return conman.saveSLA(sla);
				});
		});

		app.post('/restart', (req, res) => {
			//console.log("Restarting " + req.body.id);
			conman.getContainer(req.body.id)
				.then((container) => {
					return conman.stopContainer(container);
				})
				.then((container) => {
					return conman.startContainer(container);
				})
				.then((container) => {
					console.log('[' + container.name + '] Restarted');
					this.proxies[container.name] = container.name + ':' + container.port;
				})
				.catch((err) => {
					console.log(err);
					res.json(err);
				});
		});

		app.post('/uninstall', (req, res) => {
			//console.log("Uninstalling " + req.body.id);
			conman.getContainer(req.body.id)
				.then((container) => {
					return conman.stopContainer(container);
				})
				.then((container) => {
					return conman.removeContainer(container);
				})
				.then((info) => {
					let name = info.Name;
					if (info.Name.startsWith('/')) {
						name = info.Name.substring(1);
					}
					console.log('[' + name + '] Uninstalled');
					delete this.proxies[name];
					res.json(info);
				})
				.catch((err) => {
					console.log(err);
					res.json(err)
				});
		});

		io.on('connection', (socket) => {
			const emitter = conman.getDockerEmitter();

			emitter.on("connect", () => {
				socket.emit('docker-connect');
			});
			emitter.on("disconnect", () => {
				socket.emit('docker-disconnect');
			});
			emitter.on("_message", (message) => {
				socket.emit('docker-_message', message);
			});
			emitter.on("create", (message) => {
				socket.emit('docker-create', message);
			});
			emitter.on("start", (message) => {
				socket.emit('docker-star', message);
			});
			emitter.on("start", (message) => {
				socket.emit('docker-stop', message);
			});
			emitter.on("die", (message) => {
				socket.emit('docker-die', message);
			});
			emitter.on("destroy", (message) => {
				socket.emit('docker-destroy', message);
			});
			emitter.start();

		});

		server.listen(port);
	}
};
<|endoftext|>