<commit_before>/*jshint node: true, mocha: true, esversion: 6, expr: false, boss: true */

var sinon = require('sinon'),
    _ = require('underscore'),
    assert = require('chai').assert,
    kumascript = require('..'),
    ks_macros = kumascript.macros,
    ks_server = kumascript.server,
    ks_test_utils = kumascript.test_utils,
    testRequest = ks_test_utils.testRequest,
    readTestFixture = ks_test_utils.readTestFixture,
    testRequestExpected = ks_test_utils.testRequestExpected;

function getURL(uri) {
    return 'http://localhost:9000' + uri;
}

describe('test-server', function () {
    beforeEach(function() {
        // Build both a kumascript instance and a document server for tests.
        this.test_server = ks_test_utils.createTestServer();
        this.test_server.get('/healthz/?', function (req, res) {
            res.sendStatus(204);
        });
        this.test_server.get('/readiness/?', function (req, res) {
            res.sendStatus(204);
        });
        this.macro_processor = new ks_macros.MacroProcessor({
            macro_timeout: 500,
            loader: {
                module: __dirname + '/../lib/kumascript/loaders',
                class_name: 'FileLoader',
                options: {
                    root_dir: "tests/fixtures/templates",
                }
            }
        });
        this.server = new ks_server.Server({
            port: 9000,
            logging: false,
            document_url_template: "http://localhost:9001/documents/{path}.txt",
            macro_processor: this.macro_processor
        });
        this.server.listen();
    })

    afterEach(function () {
        // Kill all the servers on teardown.
        this.server.close();
        this.test_server.close();
    })

    it('Fetching the root returns the homepage', function (done) {
        testRequestExpected(
            getURL('/'),
            'homepage-expected.html',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('Fetching document1 from service should be processed as expected', function (done) {
        testRequestExpected(
            getURL('/docs/document1'),
            'documents/document1-expected.txt',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('Fetching 시작하기 from service should be processed as expected', function (done) {
        testRequestExpected(
            getURL('/docs/' + encodeURI('시작하기')),
            'documents/시작하기-expected.txt',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('POSTing document to service should be processed as expected', function (done) {
        var source_filename = 'documents/document1.txt';
        readTestFixture(source_filename, done, function(source) {
            testRequestExpected(
                { method: 'POST', url: getURL('/docs/'), body: source },
                'documents/document1-expected.txt',
                done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                }
            );
        });
    })

    it('Variables passed in request headers should be made available to templates', function (done) {
        function makeHeader(value, key) {
            var h_key = 'x-kumascript-env-' + key,
                d_json = JSON.stringify(value),
                data = (new Buffer(d_json, 'utf8')).toString('base64');
            return [h_key, data];
        }

        var env = {
                'locale': "en-US",
                'alpha':  "This is the alpha value",
                'beta':   "Consultez les forums dédiés de Mozilla",
                'gamma':  "コミュニティ",
                'delta':  "커뮤니티",
                'foo':    ['one', 'two', 'three'],
                'bar':    {'a':1, 'b':2, 'c':3}
            },
            headers = _.chain(env).map(makeHeader).object().value(),
            source_filename = 'documents/request-variables.txt';

        readTestFixture(source_filename, done, function(source) {
            testRequestExpected(
                { method: 'GET',
                  url: getURL('/docs/request-variables'),
                  body: source,
                  headers: headers
                },
                'documents/request-variables-expected.txt',
                done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                }
            );
        });
    })

    it('Errors in macro processing should be included in response headers', function (done) {

        var mp = this.server.macro_processor = new ks_macros.MacroProcessor({
            macro_timeout: 500,
            loader: {
                module: __dirname + '/../lib/kumascript/test-utils',
                class_name: 'LocalClassLoader',
                options: {
                    module: __dirname + '/../lib/kumascript/test-utils',
                    templates: {
                        'broken1': null,
                        'broken2': 'BrokenCompilationTemplate',
                        'broken3': 'BrokenExecutionTemplate',
                        'MacroUsingParams': 'JSONifyTemplate',
                        'AnotherFoundMacro': 'JSONifyTemplate'
                    }
                }
            }
        });

        function extractErrors(resp) {
            // First pass, assemble all the base64 log fragments
            // from headers into buckets by UID.
            var logs_pieces = {};
            _.each(resp.headers, function (value, key) {
                if (key.indexOf('firelogger-') !== 0) {
                    return;
                }
                var parts = key.split('-'),
                    uid = parts[1],
                    seq = parts[2];
                if (!(uid in logs_pieces)) {
                    logs_pieces[uid] = [];
                }
                logs_pieces[uid][seq] = value;
            });

            // Second pass, decode the base64 log fragments in each bucket.
            var logs = {};
            _.each(logs_pieces, function (pieces, uid) {
                var d_b64 = pieces.join(''),
                    d_json = (new Buffer(d_b64, 'base64')).toString('utf-8');
                logs[uid] = JSON.parse(d_json).logs;
            });

            // Third pass, extract all kumascript error messages.
            var errors = {};
            _.each(logs, function (messages, uid) {
                _.each(messages, function (m) {
                    if (m.name == 'kumascript' && m.level == 'error') {
                        errors[m.args[2].name] = m.args.slice(0, 2);
                    }
                });
            });

            return errors
        }

        mp.startup(function () {
            var req_opts = {
                    method: "GET",
                    uri: getURL('/docs/document2'),
                    headers: {
                        "X-FireLogger": "1.2"
                    }
                },
                expected_errors = {
                    'broken1': ["TemplateLoadingError",
                                "NOT FOUND"],
                    'broken2': ["TemplateLoadingError",
                                "ERROR INITIALIZING broken2"],
                    'broken3': ["TemplateExecutionError",
                                "ERROR EXECUTING broken3"]
                },
                expected_filename = 'documents/document2-expected.txt';

            testRequestExpected(req_opts, expected_filename, done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                    assert.equal(resp.headers['vary'], 'X-FireLogger');

                    var errors = extractErrors(resp);

                    // Ensure that we saw the same expected errors.
                    assert.sameMembers(_.keys(errors),
                                       _.keys(expected_errors));
                    // Check the error values against what is expected.
                    _.each(expected_errors, function (expected, name) {
                        var error = errors[name];
                        assert.equal(error[0], expected[0]);
                        assert.isTrue(error[1].indexOf(expected[1]) !== -1);
                    });
                }
            );
        });
    })

    it('Error fetching source document should be logged', function (done) {
        // Induce error condition by closing down the test server.
        this.test_server.close();

        var expected_err =
                'Problem fetching source document: connect ECONNREFUSED',
            req_opts = {
                method: "GET",
                uri: getURL('/docs/error-doc'),
                headers: {
                    "X-FireLogger": "plaintext"
                }
            };

        testRequest(req_opts, done, function (resp, result) {
            // Look for the expected error message in the headers.
            var found_it = false;
            _.each(resp.headers, function (value, key) {
                if (key.indexOf('firelogger-') === -1) {
                    return;
                }
                if (value.indexOf(expected_err) !== -1) {
                    found_it = true;
                }
            });
            assert.isTrue(found_it);
        });
    })

    it('Fetching /macros returns macro details', function (done) {
        testRequestExpected(getURL('/macros'), 'macros-expected.json', done,
            function(resp, result, expected) {
                assert.deepEqual(JSON.parse(result), JSON.parse(expected));
            }
        );
    })

    it('Liveness endpoint returns 204 when live', function (done) {
        testRequest(getURL('/healthz'), done, function (resp, result) {
            assert.equal(resp.statusCode, 204);
        });
    });

    it('Readiness endpoint returns 204 when ready', function (done) {
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 204);
        });
    });

    it('Readiness endpoint returns 503 when macro loader failure', function (done) {
        this.macro_processor.makeLoader = sinon.stub();
        this.macro_processor.makeLoader.throws('duplicate macros');
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(result.indexOf('(macro loader error)'), -1);
            assert.notEqual(result.indexOf('(duplicate macros)'), -1);
        });
    });

    it('Readiness endpoint returns 503 when doc service failure', function (done) {
        this.test_server.close();
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(
                result.indexOf('(document service is not ready)'),
                -1
            );
        });
    });

    it('Readiness endpoint returns 503 when doc service not ready', function (done) {
        this.test_server.close();
        this.test_server = ks_test_utils.createTestServer();
        this.test_server.get('/readiness/?', function (req, res) {
            res.status(503).send('service unavailable due to database issue');
        });
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(
                result.indexOf('(document service is not ready)'),
                -1
            );
            assert.notEqual(
                result.indexOf('(service unavailable due to database issue)'),
                -1
            );
        });
    });
});
<commit_msg>remove unused liveness endpoint from test setup<commit_after>/*jshint node: true, mocha: true, esversion: 6, expr: false, boss: true */

var sinon = require('sinon'),
    _ = require('underscore'),
    assert = require('chai').assert,
    kumascript = require('..'),
    ks_macros = kumascript.macros,
    ks_server = kumascript.server,
    ks_test_utils = kumascript.test_utils,
    testRequest = ks_test_utils.testRequest,
    readTestFixture = ks_test_utils.readTestFixture,
    testRequestExpected = ks_test_utils.testRequestExpected;

function getURL(uri) {
    return 'http://localhost:9000' + uri;
}

describe('test-server', function () {
    beforeEach(function() {
        // Build both a kumascript instance and a document server for tests.
        this.test_server = ks_test_utils.createTestServer();
        this.test_server.get('/readiness/?', function (req, res) {
            res.sendStatus(204);
        });
        this.macro_processor = new ks_macros.MacroProcessor({
            macro_timeout: 500,
            loader: {
                module: __dirname + '/../lib/kumascript/loaders',
                class_name: 'FileLoader',
                options: {
                    root_dir: "tests/fixtures/templates",
                }
            }
        });
        this.server = new ks_server.Server({
            port: 9000,
            logging: false,
            document_url_template: "http://localhost:9001/documents/{path}.txt",
            macro_processor: this.macro_processor
        });
        this.server.listen();
    })

    afterEach(function () {
        // Kill all the servers on teardown.
        this.server.close();
        this.test_server.close();
    })

    it('Fetching the root returns the homepage', function (done) {
        testRequestExpected(
            getURL('/'),
            'homepage-expected.html',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('Fetching document1 from service should be processed as expected', function (done) {
        testRequestExpected(
            getURL('/docs/document1'),
            'documents/document1-expected.txt',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('Fetching 시작하기 from service should be processed as expected', function (done) {
        testRequestExpected(
            getURL('/docs/' + encodeURI('시작하기')),
            'documents/시작하기-expected.txt',
            done,
            function(resp, result, expected) {
                assert.equal(result.trim(), expected.trim());
            }
        );
    })

    it('POSTing document to service should be processed as expected', function (done) {
        var source_filename = 'documents/document1.txt';
        readTestFixture(source_filename, done, function(source) {
            testRequestExpected(
                { method: 'POST', url: getURL('/docs/'), body: source },
                'documents/document1-expected.txt',
                done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                }
            );
        });
    })

    it('Variables passed in request headers should be made available to templates', function (done) {
        function makeHeader(value, key) {
            var h_key = 'x-kumascript-env-' + key,
                d_json = JSON.stringify(value),
                data = (new Buffer(d_json, 'utf8')).toString('base64');
            return [h_key, data];
        }

        var env = {
                'locale': "en-US",
                'alpha':  "This is the alpha value",
                'beta':   "Consultez les forums dédiés de Mozilla",
                'gamma':  "コミュニティ",
                'delta':  "커뮤니티",
                'foo':    ['one', 'two', 'three'],
                'bar':    {'a':1, 'b':2, 'c':3}
            },
            headers = _.chain(env).map(makeHeader).object().value(),
            source_filename = 'documents/request-variables.txt';

        readTestFixture(source_filename, done, function(source) {
            testRequestExpected(
                { method: 'GET',
                  url: getURL('/docs/request-variables'),
                  body: source,
                  headers: headers
                },
                'documents/request-variables-expected.txt',
                done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                }
            );
        });
    })

    it('Errors in macro processing should be included in response headers', function (done) {

        var mp = this.server.macro_processor = new ks_macros.MacroProcessor({
            macro_timeout: 500,
            loader: {
                module: __dirname + '/../lib/kumascript/test-utils',
                class_name: 'LocalClassLoader',
                options: {
                    module: __dirname + '/../lib/kumascript/test-utils',
                    templates: {
                        'broken1': null,
                        'broken2': 'BrokenCompilationTemplate',
                        'broken3': 'BrokenExecutionTemplate',
                        'MacroUsingParams': 'JSONifyTemplate',
                        'AnotherFoundMacro': 'JSONifyTemplate'
                    }
                }
            }
        });

        function extractErrors(resp) {
            // First pass, assemble all the base64 log fragments
            // from headers into buckets by UID.
            var logs_pieces = {};
            _.each(resp.headers, function (value, key) {
                if (key.indexOf('firelogger-') !== 0) {
                    return;
                }
                var parts = key.split('-'),
                    uid = parts[1],
                    seq = parts[2];
                if (!(uid in logs_pieces)) {
                    logs_pieces[uid] = [];
                }
                logs_pieces[uid][seq] = value;
            });

            // Second pass, decode the base64 log fragments in each bucket.
            var logs = {};
            _.each(logs_pieces, function (pieces, uid) {
                var d_b64 = pieces.join(''),
                    d_json = (new Buffer(d_b64, 'base64')).toString('utf-8');
                logs[uid] = JSON.parse(d_json).logs;
            });

            // Third pass, extract all kumascript error messages.
            var errors = {};
            _.each(logs, function (messages, uid) {
                _.each(messages, function (m) {
                    if (m.name == 'kumascript' && m.level == 'error') {
                        errors[m.args[2].name] = m.args.slice(0, 2);
                    }
                });
            });

            return errors
        }

        mp.startup(function () {
            var req_opts = {
                    method: "GET",
                    uri: getURL('/docs/document2'),
                    headers: {
                        "X-FireLogger": "1.2"
                    }
                },
                expected_errors = {
                    'broken1': ["TemplateLoadingError",
                                "NOT FOUND"],
                    'broken2': ["TemplateLoadingError",
                                "ERROR INITIALIZING broken2"],
                    'broken3': ["TemplateExecutionError",
                                "ERROR EXECUTING broken3"]
                },
                expected_filename = 'documents/document2-expected.txt';

            testRequestExpected(req_opts, expected_filename, done,
                function(resp, result, expected) {
                    assert.equal(result.trim(), expected.trim());
                    assert.equal(resp.headers['vary'], 'X-FireLogger');

                    var errors = extractErrors(resp);

                    // Ensure that we saw the same expected errors.
                    assert.sameMembers(_.keys(errors),
                                       _.keys(expected_errors));
                    // Check the error values against what is expected.
                    _.each(expected_errors, function (expected, name) {
                        var error = errors[name];
                        assert.equal(error[0], expected[0]);
                        assert.isTrue(error[1].indexOf(expected[1]) !== -1);
                    });
                }
            );
        });
    })

    it('Error fetching source document should be logged', function (done) {
        // Induce error condition by closing down the test server.
        this.test_server.close();

        var expected_err =
                'Problem fetching source document: connect ECONNREFUSED',
            req_opts = {
                method: "GET",
                uri: getURL('/docs/error-doc'),
                headers: {
                    "X-FireLogger": "plaintext"
                }
            };

        testRequest(req_opts, done, function (resp, result) {
            // Look for the expected error message in the headers.
            var found_it = false;
            _.each(resp.headers, function (value, key) {
                if (key.indexOf('firelogger-') === -1) {
                    return;
                }
                if (value.indexOf(expected_err) !== -1) {
                    found_it = true;
                }
            });
            assert.isTrue(found_it);
        });
    })

    it('Fetching /macros returns macro details', function (done) {
        testRequestExpected(getURL('/macros'), 'macros-expected.json', done,
            function(resp, result, expected) {
                assert.deepEqual(JSON.parse(result), JSON.parse(expected));
            }
        );
    })

    it('Liveness endpoint returns 204 when live', function (done) {
        testRequest(getURL('/healthz'), done, function (resp, result) {
            assert.equal(resp.statusCode, 204);
        });
    });

    it('Readiness endpoint returns 204 when ready', function (done) {
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 204);
        });
    });

    it('Readiness endpoint returns 503 when macro loader failure', function (done) {
        this.macro_processor.makeLoader = sinon.stub();
        this.macro_processor.makeLoader.throws('duplicate macros');
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(result.indexOf('(macro loader error)'), -1);
            assert.notEqual(result.indexOf('(duplicate macros)'), -1);
        });
    });

    it('Readiness endpoint returns 503 when doc service failure', function (done) {
        this.test_server.close();
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(
                result.indexOf('(document service is not ready)'),
                -1
            );
        });
    });

    it('Readiness endpoint returns 503 when doc service not ready', function (done) {
        this.test_server.close();
        this.test_server = ks_test_utils.createTestServer();
        this.test_server.get('/readiness/?', function (req, res) {
            res.status(503).send('service unavailable due to database issue');
        });
        testRequest(getURL('/readiness'), done, function (resp, result) {
            assert.equal(resp.statusCode, 503);
            assert.notEqual(
                result.indexOf('(document service is not ready)'),
                -1
            );
            assert.notEqual(
                result.indexOf('(service unavailable due to database issue)'),
                -1
            );
        });
    });
});
<|endoftext|>


<commit_before>const fs = require('fs');
const p = require('path');

// walk $PATH to find bin
const which = bin => {
    const path = process.env.PATH.split(p.delimiter);
    for (let i in path) {
        const file = path[i] + p.sep + bin;
        if (fs.existsSync(file)) return file;
    }
    return '';
};

module.exports = {
    remove_infected: false,                         // don't change
    quarantine_infected: __dirname + '/infected',   // required for testing
    //scan_log: __dirname + '/clamscan-log',        // not required
    clamscan: {
        path: which('clamscan'),                    // required for testing
    },
    clamdscan: {
        socket: '/var/run/clamd.scan/clamd.sock',   // required for testing (change for your system) - can be set to null
        // socket: '/var/run/clamav/clamd.ctl',        // required for testing (change for your system) - can be set to null
        host: '127.0.0.1',                          // required for testing (change for your system) - can be set to null
        port: 3310,                                 // required for testing (change for your system) - can be set to null
        path: which('clamdscan'),                   // required for testing
        //config_file: '/etc/clamd.d/scan.conf'     // set if required
    },
    debug_mode: false
};
<commit_msg>Add example of common clamdscan socket alternative in test_config comment.<commit_after>const fs = require('fs');
const p = require('path');

// walk $PATH to find bin
const which = bin => {
    const path = process.env.PATH.split(p.delimiter);
    for (let i in path) {
        const file = path[i] + p.sep + bin;
        if (fs.existsSync(file)) return file;
    }
    return '';
};

module.exports = {
    remove_infected: false,                         // don't change
    quarantine_infected: __dirname + '/infected',   // required for testing
    //scan_log: __dirname + '/clamscan-log',        // not required
    clamscan: {
        path: which('clamscan'),                    // required for testing
    },
    clamdscan: {
        socket: '/var/run/clamav/clamd.ctl',        // required for testing (change for your system e.g. '/var/run/clamd.scan/clamd.sock') - can be set to null
        host: '127.0.0.1',                          // required for testing (change for your system) - can be set to null
        port: 3310,                                 // required for testing (change for your system) - can be set to null
        path: which('clamdscan'),                   // required for testing
        //config_file: '/etc/clamd.d/scan.conf'     // set if required
    },
    debug_mode: false
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 *
 * This file implements a multi-device proxy for the Chrome debugging protocol. Each device connects
 * to the proxy over a single websocket connection that is able to multiplex messages to multiple
 * Javascript VMs. An inspector instance running in Chrome can connect to a specific VM via this
 * proxy.
 *
 * The connection to from device to the proxy uses a simple JSON-based protocol with the basic
 * structure { event: '<event-name>', payload: { ... }}. All events are send without acknowledgment
 * except the 'getPages' event where the device responds with the available pages. Most events will
 * be wrapped inspector events going to a specific 'page' on the device.
 *
 * See below for a diagram of how the devices, inspector(s) and proxy interact.
 *           +--------+
 *           | Device |                                            +------------+
 *           |   #1   |               +---------+                  |Chrome      |
 *           |        |               |         | Chrome debugging +------------+
 *           +--+--+  |    Custom     |         |     protocol     |Inspector   |
 *           |VM|VM|  |------+------->|  Proxy  |<----------+------|            |
 *           +--+--+--+      |        |         |           |      |            |
 *                           |        |         |           |      +------------+
 *           +--------+      |        +---------+           |      |Inspector   |
 *           | Device |------+                              +------|            |
 *           |   #2   |                                            |            |
 *           |        |                                            +------------+
 *           +--+     |
 *           |VM|     |
 *           +--+-----+
 */
'use strict';

const flatMapArray = require('fbjs/lib/flatMapArray');
const http = require('http');
const nullthrows = require('fbjs/lib/nullthrows');
const querystring = require('querystring');

const parseUrl = require('url').parse;
const WebSocket = require('ws');

const debug = require('debug')('RNP:InspectorProxy');
const launchChrome = require('./launchChrome');

type DevicePage = {
  id: string,
  title: string,
};

type Page = {
  id: string,
  title: string,
  description: string,
  devtoolsFrontendUrl: string,
  webSocketDebuggerUrl: string,
  deviceId: string,
  deviceName: string,
};

type Message<Name, Payload> = {
  event?: Name,
  payload?: Payload,
};

type WrappedEvent = Message<'wrappedEvent', {
  pageId?: string,
  wrappedEvent?: string,
}>;

type ConnectEvent = Message<'connect', {
  pageId?: string,
}>;

type DisconnectEvent = Message<'disconnect', {
  pageId?: string,
}>;

type OpenEvent = Message<'open', {
  pageId?: string,
}>;

type GetPages = Message<'getPages', ?Array<DevicePage>>;

type Event = WrappedEvent | ConnectEvent | DisconnectEvent | GetPages;

type Address = {
  address: string,
  port: number,
};

const DEVICE_TIMEOUT = 30000;

// FIXME: This is the url we want to use as it more closely matches the actual protocol we use.
// However, it's broken in Chrome 54+ due to it using 'KeyboardEvent.keyIdentifier'.
// const DEVTOOLS_URL_BASE = 'https://chrome-devtools-frontend.appspot.com/serve_rev/@178469/devtools.html?ws=';
const DEVTOOLS_URL_BASE = 'https://chrome-devtools-frontend.appspot.com/serve_file/@60cd6e859b9f557d2312f5bf532f6aec5f284980/inspector.html?ws=';

class Device {
  name: string;

  _id: string;
  _socket: WebSocket;
  _handlers: Map<string, (result?: Object) => void>;
  _connections: Map<string, WebSocket>;

  constructor(id: string, name: string, socket: WebSocket) {
    this.name = name;
    this._id = id;
    this._socket = socket;
    this._handlers = new Map();
    this._connections = new Map();

    this._socket.on('message', this._onMessage.bind(this));
    this._socket.on('close', this._onDeviceDisconnected.bind(this));
  }

  getPages(): Promise<Array<DevicePage>> {
    return this._callMethod('getPages');
  }

  connect(pageId: string, socket: WebSocket) {
    socket.on('message', (message: string) => {
      if (!this._connections.has(pageId)) {
        // Not connected, silently ignoring
        return;
      }

      // TODO: This should be handled way earlier, preferably in the inspector itself.
      // That is how it works it newer versions but it requires installing hooks.
      if (message.indexOf('Network.loadResourceForFrontend') !== -1) {
        this._loadResourceForFrontend(socket, JSON.parse(message));
        return;
      }

      this._send({
        event: 'wrappedEvent',
        payload: {
          pageId,
          wrappedEvent: message,
        },
      });
    });
    socket.on('close', () => {
      if (this._connections.has(pageId)) {
        this._send({event: 'disconnect', payload: {pageId: pageId}});
        this._removeConnection(pageId);
      }
    });
    this._connections.set(pageId, socket);
    this._send({event: 'connect', payload: {pageId: pageId}});
  }

  _callMethod(name: 'getPages'): Promise<any> {
    const promise = new Promise((fulfill, reject) => {
      const timerId = setTimeout(() => {
        this._handlers.delete(name);
        reject(new Error('Timeout waiting for device'));
      }, DEVICE_TIMEOUT);
      this._handlers.set(name, (...args) => {
        clearTimeout(timerId);
        fulfill(...args);
      });
    });
    this._send({event: name});
    return promise;
  }

  _send(message: Event) {
    debug('-> device', this._id, message);
    // This try/catch is unfortunate, but there is a small window where a message can be sent
    // 1. after the socket is closed, and
    // 2. before the callback for the 'close' event on the socket is run.
    // Since we don't want the packager to crash in this situation, we have to guard against this.
    try {
      this._socket.send(JSON.stringify(message));
    } catch (err) {
      debug('Error sending', err);
    }
  }

  _onMessage(json: string) {
    debug('<- device', this._id, json);
    const message = JSON.parse(json);
    const handler = this._handlers.get(message.event);
    if (handler) {
      this._handlers.delete(message.event);
      handler(message.payload);
      return;
    }

    if (message.event === 'wrappedEvent') {
      this._handleWrappedEvent(message);
    } else if (message.event === 'disconnect') {
      this._handleDisconnect(message);
    } else if (message.event === 'open') {
      this._handleOpen(message);
    }
  }

  _handleWrappedEvent(event: WrappedEvent) {
    const payload = nullthrows(event.payload);
    const socket = this._connections.get(nullthrows(payload.pageId));
    if (!socket) {
      console.error('Invalid pageId from device:', payload.pageId);
      return;
    }
    socket.send(payload.wrappedEvent);
  }

  _handleDisconnect(event: DisconnectEvent) {
    const payload = nullthrows(event.payload);
    const pageId = nullthrows(payload.pageId);
    this._removeConnection(pageId);
  }

  _handleOpen(event: OpenEvent) {
    const payload = nullthrows(event.payload);
    const pageId = nullthrows(payload.pageId);
    const url = DEVTOOLS_URL_BASE + makeInspectorPageUrl(this._id, pageId);
    launchChrome(url);
  }

  _removeConnection(pageId: string) {
    const socket = this._connections.get(pageId);
    if (socket) {
      this._connections.delete(pageId);
      socket.close();
    }
  }

  _onDeviceDisconnected() {
    for (const pageId of this._connections.keys()) {
      this._removeConnection(pageId);
    }
  }

  _loadResourceForFrontend(socket: WebSocket, event: Object) {
    const id: number = nullthrows(event.id);
    const url: string = nullthrows(nullthrows(event.params).url);
    debug('loadResourceForFrontend:', url);
    http.get(this._normalizeUrl(url), (response) => {
      // $FlowFixMe callback is optional
      response.setTimeout(0);
      let data = '';
      response.on('data', (chunk) => { data += chunk; });
      response.on('end', () => {
        socket.send(JSON.stringify({
          id: id,
          result: {
            statusCode: response.statusCode,
            content: data,
            responseHeaders: response.headers,
          },
        }));
      });
      response.on('error', (error) => {
        console.error('Failed to get resource', error);
      });
    });
  }

  _normalizeUrl(url: string): string {
    return url.replace('http://10.0.3.2', 'http://localhost')
      .replace('http://10.0.2.2', 'http://localhost');
  }
}

class InspectorProxy {
  _devices: Map<string, Device>;
  _devicesCounter: number;

  constructor() {
    this._devices = new Map();
    this._devicesCounter = 0;
  }

  attachToServer(server: http.Server, pathPrefix: string) {
    this._createPageHandler(server, pathPrefix + '/page');
    this._createDeviceHandler(server, pathPrefix + '/device');
    this._createPagesListHandler(server, pathPrefix + '/');
    this._createPagesJsonHandler(server, pathPrefix + '/json');
  }

  _makePage(server: Address, deviceId: string, deviceName: string, devicePage: DevicePage): Page {
    const wsUrl = makeInspectorPageUrl(deviceId, devicePage.id);
    return {
      id: `${deviceId}-${devicePage.id}`,
      title: devicePage.title,
      description: '',
      devtoolsFrontendUrl: DEVTOOLS_URL_BASE + wsUrl,
      webSocketDebuggerUrl: `ws://${wsUrl}`,
      deviceId,
      deviceName,
    };
  }

  _getPages(localAddress: Address): Promise<Array<Page>> {
    const promises = Array.from(this._devices.entries(), ([deviceId, device]) => {
      return device.getPages().then((devicePages) => {
        return devicePages.map(this._makePage.bind(this, localAddress, deviceId, device.name));
      });
    });

    const flatMap = (arr) => flatMapArray(arr, (x) => x);
    return Promise.all(promises).then(flatMap);
  }

  processRequest(req: any, res: any, next: any) {
    // TODO: Might wanna actually do the handling here
    const endpoints = [
      '/inspector/',
      '/inspector/page',
      '/inspector/device',
      '/inspector/json',
    ];
    if (endpoints.indexOf(req.url) === -1) {
      next();
    }
  }

  _createDeviceHandler(server: http.Server, path: string) {
    const wss = new WebSocket.Server({
      server,
      path,
    });
    wss.on('connection', (socket: WebSocket) => {
      try {
        const query = parseUrl(socket.upgradeReq.url, true).query || {};
        const deviceName = query.name || 'Unknown';
        debug('Got device connection:', deviceName);
        const deviceId = String(this._devicesCounter++);
        const device = new Device(deviceId, deviceName, socket);
        this._devices.set(deviceId, device);
        socket.on('close', () => {
          this._devices.delete(deviceId);
        });
      } catch (e) {
        console.error(e);
        socket.close(1011, e.message);
      }
    });
  }

  _createPageHandler(server: http.Server, path: string) {
    const wss = new WebSocket.Server({
      server,
      path,
    });
    wss.on('connection', (socket: WebSocket) => {
      try {
        const url = parseUrl(socket.upgradeReq.url, false);
        const { device, page } = querystring.parse(
          querystring.unescape(nullthrows(url.query)));
        if (device === undefined || page === undefined) {
          throw Error('Must provide device and page');
        }
        const deviceObject = this._devices.get(device);
        if (!deviceObject) {
          throw Error('Unknown device: ' + device);
        }

        deviceObject.connect(page, socket);
      } catch (e) {
        console.error(e);
        socket.close(1011, e.message);
      }
    });
  }

  _createPagesJsonHandler(server: http.Server, path: string) {
    server.on('request', (request: http.IncomingMessage, response: http.ServerResponse) => {
      if (request.url === path) {
        this._getPages(server.address()).then((result: Array<Page>) => {
          response.writeHead(200, {'Content-Type': 'application/json'});
          response.end(JSON.stringify(result));
        }, (error: Error) => {
          response.writeHead(500);
          response.end('Internal error: ' + error.toString());
        });
      }
    });
  }

  _createPagesListHandler(server: http.Server, path: string) {
    server.on('request', (request: http.IncomingMessage, response: http.ServerResponse) => {
      if (request.url === path) {
        this._getPages(server.address()).then((result: Array<Page>) => {
          response.writeHead(200, {'Content-Type': 'text/html'});
          response.end(buildPagesHtml(result));
        }, (error: Error) => {
          response.writeHead(500);
          response.end('Internal error: ' + error.toString());
        });
      }
    });
  }

}

function buildPagesHtml(pages: Array<Page>): string {
  const pagesHtml = pages.map((page) => {
    return escapeHtml`

          ${page.deviceName} / ${page.title}

    `;
  }).join('\n');

  return `

      <title>Pages</title>

Pages

          ${pagesHtml}

  `;
}

function escapeHtml(pieces: Array<string>, ...substitutions: Array<string>): string {
  let result = pieces[0];
  for (let i = 0; i < substitutions.length; ++i) {
    result += substitutions[i].replace(/[<&"'>]/g, escapeHtmlSpecialChar) + pieces[i + 1];
  }

  return result;
}

function escapeHtmlSpecialChar(char: string): string {
  return (
    char === '&' ? '&' :
    char === '"' ? '"' :
    char === "'" ? ''' :
    char === '<' ? '<' :
    char === '>' ? '>' :
    char
  );
}

function makeInspectorPageUrl(deviceId: string, pageId: string): string {
  // The inspector frontend doesn't handle urlencoded params so we
  // manually urlencode it and decode it on the other side in _createPageHandler
  const query = querystring.escape(`device=${deviceId}&page=${pageId}`);
  return `localhost:8081/inspector/page?${query}`;
}

function attachToServer(server: http.Server, pathPrefix: string): InspectorProxy {
  const proxy = new InspectorProxy();
  proxy.attachToServer(server, pathPrefix);
  return proxy;
}

if (!module.parent) {
  console.info('Starting server');
  process.env.DEBUG = 'RNP:Inspector';
  const serverInstance = http.createServer().listen(
    8081,
    'localhost',
    undefined,
    function() {
      attachToServer(serverInstance, '/inspector');
    }
  );
  serverInstance.timeout = 0;
}

// module.exports.attachToServer = attachToServer;
module.exports = InspectorProxy;
<commit_msg>Fix Promise resolve callback error<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 *
 * This file implements a multi-device proxy for the Chrome debugging protocol. Each device connects
 * to the proxy over a single websocket connection that is able to multiplex messages to multiple
 * Javascript VMs. An inspector instance running in Chrome can connect to a specific VM via this
 * proxy.
 *
 * The connection to from device to the proxy uses a simple JSON-based protocol with the basic
 * structure { event: '<event-name>', payload: { ... }}. All events are send without acknowledgment
 * except the 'getPages' event where the device responds with the available pages. Most events will
 * be wrapped inspector events going to a specific 'page' on the device.
 *
 * See below for a diagram of how the devices, inspector(s) and proxy interact.
 *           +--------+
 *           | Device |                                            +------------+
 *           |   #1   |               +---------+                  |Chrome      |
 *           |        |               |         | Chrome debugging +------------+
 *           +--+--+  |    Custom     |         |     protocol     |Inspector   |
 *           |VM|VM|  |------+------->|  Proxy  |<----------+------|            |
 *           +--+--+--+      |        |         |           |      |            |
 *                           |        |         |           |      +------------+
 *           +--------+      |        +---------+           |      |Inspector   |
 *           | Device |------+                              +------|            |
 *           |   #2   |                                            |            |
 *           |        |                                            +------------+
 *           +--+     |
 *           |VM|     |
 *           +--+-----+
 */
'use strict';

const flatMapArray = require('fbjs/lib/flatMapArray');
const http = require('http');
const nullthrows = require('fbjs/lib/nullthrows');
const querystring = require('querystring');

const parseUrl = require('url').parse;
const WebSocket = require('ws');

const debug = require('debug')('RNP:InspectorProxy');
const launchChrome = require('./launchChrome');

type DevicePage = {
  id: string,
  title: string,
};

type Page = {
  id: string,
  title: string,
  description: string,
  devtoolsFrontendUrl: string,
  webSocketDebuggerUrl: string,
  deviceId: string,
  deviceName: string,
};

type Message<Name, Payload> = {
  event?: Name,
  payload?: Payload,
};

type WrappedEvent = Message<'wrappedEvent', {
  pageId?: string,
  wrappedEvent?: string,
}>;

type ConnectEvent = Message<'connect', {
  pageId?: string,
}>;

type DisconnectEvent = Message<'disconnect', {
  pageId?: string,
}>;

type OpenEvent = Message<'open', {
  pageId?: string,
}>;

type GetPages = Message<'getPages', ?Array<DevicePage>>;

type Event = WrappedEvent | ConnectEvent | DisconnectEvent | GetPages;

type Address = {
  address: string,
  port: number,
};

const DEVICE_TIMEOUT = 30000;

// FIXME: This is the url we want to use as it more closely matches the actual protocol we use.
// However, it's broken in Chrome 54+ due to it using 'KeyboardEvent.keyIdentifier'.
// const DEVTOOLS_URL_BASE = 'https://chrome-devtools-frontend.appspot.com/serve_rev/@178469/devtools.html?ws=';
const DEVTOOLS_URL_BASE = 'https://chrome-devtools-frontend.appspot.com/serve_file/@60cd6e859b9f557d2312f5bf532f6aec5f284980/inspector.html?ws=';

class Device {
  name: string;

  _id: string;
  _socket: WebSocket;
  _handlers: Map<string, (result?: Object) => void>;
  _connections: Map<string, WebSocket>;

  constructor(id: string, name: string, socket: WebSocket) {
    this.name = name;
    this._id = id;
    this._socket = socket;
    this._handlers = new Map();
    this._connections = new Map();

    this._socket.on('message', this._onMessage.bind(this));
    this._socket.on('close', this._onDeviceDisconnected.bind(this));
  }

  getPages(): Promise<Array<DevicePage>> {
    return this._callMethod('getPages');
  }

  connect(pageId: string, socket: WebSocket) {
    socket.on('message', (message: string) => {
      if (!this._connections.has(pageId)) {
        // Not connected, silently ignoring
        return;
      }

      // TODO: This should be handled way earlier, preferably in the inspector itself.
      // That is how it works it newer versions but it requires installing hooks.
      if (message.indexOf('Network.loadResourceForFrontend') !== -1) {
        this._loadResourceForFrontend(socket, JSON.parse(message));
        return;
      }

      this._send({
        event: 'wrappedEvent',
        payload: {
          pageId,
          wrappedEvent: message,
        },
      });
    });
    socket.on('close', () => {
      if (this._connections.has(pageId)) {
        this._send({event: 'disconnect', payload: {pageId: pageId}});
        this._removeConnection(pageId);
      }
    });
    this._connections.set(pageId, socket);
    this._send({event: 'connect', payload: {pageId: pageId}});
  }

  _callMethod(name: 'getPages'): Promise<any> {
    const promise = new Promise((fulfill, reject) => {
      const timerId = setTimeout(() => {
        this._handlers.delete(name);
        reject(new Error('Timeout waiting for device'));
      }, DEVICE_TIMEOUT);
      this._handlers.set(name, arg => {
        clearTimeout(timerId);
        fulfill(arg);
      });
    });
    this._send({event: name});
    return promise;
  }

  _send(message: Event) {
    debug('-> device', this._id, message);
    // This try/catch is unfortunate, but there is a small window where a message can be sent
    // 1. after the socket is closed, and
    // 2. before the callback for the 'close' event on the socket is run.
    // Since we don't want the packager to crash in this situation, we have to guard against this.
    try {
      this._socket.send(JSON.stringify(message));
    } catch (err) {
      debug('Error sending', err);
    }
  }

  _onMessage(json: string) {
    debug('<- device', this._id, json);
    const message = JSON.parse(json);
    const handler = this._handlers.get(message.event);
    if (handler) {
      this._handlers.delete(message.event);
      handler(message.payload);
      return;
    }

    if (message.event === 'wrappedEvent') {
      this._handleWrappedEvent(message);
    } else if (message.event === 'disconnect') {
      this._handleDisconnect(message);
    } else if (message.event === 'open') {
      this._handleOpen(message);
    }
  }

  _handleWrappedEvent(event: WrappedEvent) {
    const payload = nullthrows(event.payload);
    const socket = this._connections.get(nullthrows(payload.pageId));
    if (!socket) {
      console.error('Invalid pageId from device:', payload.pageId);
      return;
    }
    socket.send(payload.wrappedEvent);
  }

  _handleDisconnect(event: DisconnectEvent) {
    const payload = nullthrows(event.payload);
    const pageId = nullthrows(payload.pageId);
    this._removeConnection(pageId);
  }

  _handleOpen(event: OpenEvent) {
    const payload = nullthrows(event.payload);
    const pageId = nullthrows(payload.pageId);
    const url = DEVTOOLS_URL_BASE + makeInspectorPageUrl(this._id, pageId);
    launchChrome(url);
  }

  _removeConnection(pageId: string) {
    const socket = this._connections.get(pageId);
    if (socket) {
      this._connections.delete(pageId);
      socket.close();
    }
  }

  _onDeviceDisconnected() {
    for (const pageId of this._connections.keys()) {
      this._removeConnection(pageId);
    }
  }

  _loadResourceForFrontend(socket: WebSocket, event: Object) {
    const id: number = nullthrows(event.id);
    const url: string = nullthrows(nullthrows(event.params).url);
    debug('loadResourceForFrontend:', url);
    http.get(this._normalizeUrl(url), (response) => {
      // $FlowFixMe callback is optional
      response.setTimeout(0);
      let data = '';
      response.on('data', (chunk) => { data += chunk; });
      response.on('end', () => {
        socket.send(JSON.stringify({
          id: id,
          result: {
            statusCode: response.statusCode,
            content: data,
            responseHeaders: response.headers,
          },
        }));
      });
      response.on('error', (error) => {
        console.error('Failed to get resource', error);
      });
    });
  }

  _normalizeUrl(url: string): string {
    return url.replace('http://10.0.3.2', 'http://localhost')
      .replace('http://10.0.2.2', 'http://localhost');
  }
}

class InspectorProxy {
  _devices: Map<string, Device>;
  _devicesCounter: number;

  constructor() {
    this._devices = new Map();
    this._devicesCounter = 0;
  }

  attachToServer(server: http.Server, pathPrefix: string) {
    this._createPageHandler(server, pathPrefix + '/page');
    this._createDeviceHandler(server, pathPrefix + '/device');
    this._createPagesListHandler(server, pathPrefix + '/');
    this._createPagesJsonHandler(server, pathPrefix + '/json');
  }

  _makePage(server: Address, deviceId: string, deviceName: string, devicePage: DevicePage): Page {
    const wsUrl = makeInspectorPageUrl(deviceId, devicePage.id);
    return {
      id: `${deviceId}-${devicePage.id}`,
      title: devicePage.title,
      description: '',
      devtoolsFrontendUrl: DEVTOOLS_URL_BASE + wsUrl,
      webSocketDebuggerUrl: `ws://${wsUrl}`,
      deviceId,
      deviceName,
    };
  }

  _getPages(localAddress: Address): Promise<Array<Page>> {
    const promises = Array.from(this._devices.entries(), ([deviceId, device]) => {
      return device.getPages().then((devicePages) => {
        return devicePages.map(this._makePage.bind(this, localAddress, deviceId, device.name));
      });
    });

    const flatMap = (arr) => flatMapArray(arr, (x) => x);
    return Promise.all(promises).then(flatMap);
  }

  processRequest(req: any, res: any, next: any) {
    // TODO: Might wanna actually do the handling here
    const endpoints = [
      '/inspector/',
      '/inspector/page',
      '/inspector/device',
      '/inspector/json',
    ];
    if (endpoints.indexOf(req.url) === -1) {
      next();
    }
  }

  _createDeviceHandler(server: http.Server, path: string) {
    const wss = new WebSocket.Server({
      server,
      path,
    });
    wss.on('connection', (socket: WebSocket) => {
      try {
        const query = parseUrl(socket.upgradeReq.url, true).query || {};
        const deviceName = query.name || 'Unknown';
        debug('Got device connection:', deviceName);
        const deviceId = String(this._devicesCounter++);
        const device = new Device(deviceId, deviceName, socket);
        this._devices.set(deviceId, device);
        socket.on('close', () => {
          this._devices.delete(deviceId);
        });
      } catch (e) {
        console.error(e);
        socket.close(1011, e.message);
      }
    });
  }

  _createPageHandler(server: http.Server, path: string) {
    const wss = new WebSocket.Server({
      server,
      path,
    });
    wss.on('connection', (socket: WebSocket) => {
      try {
        const url = parseUrl(socket.upgradeReq.url, false);
        const { device, page } = querystring.parse(
          querystring.unescape(nullthrows(url.query)));
        if (device === undefined || page === undefined) {
          throw Error('Must provide device and page');
        }
        const deviceObject = this._devices.get(device);
        if (!deviceObject) {
          throw Error('Unknown device: ' + device);
        }

        deviceObject.connect(page, socket);
      } catch (e) {
        console.error(e);
        socket.close(1011, e.message);
      }
    });
  }

  _createPagesJsonHandler(server: http.Server, path: string) {
    server.on('request', (request: http.IncomingMessage, response: http.ServerResponse) => {
      if (request.url === path) {
        this._getPages(server.address()).then((result: Array<Page>) => {
          response.writeHead(200, {'Content-Type': 'application/json'});
          response.end(JSON.stringify(result));
        }, (error: Error) => {
          response.writeHead(500);
          response.end('Internal error: ' + error.toString());
        });
      }
    });
  }

  _createPagesListHandler(server: http.Server, path: string) {
    server.on('request', (request: http.IncomingMessage, response: http.ServerResponse) => {
      if (request.url === path) {
        this._getPages(server.address()).then((result: Array<Page>) => {
          response.writeHead(200, {'Content-Type': 'text/html'});
          response.end(buildPagesHtml(result));
        }, (error: Error) => {
          response.writeHead(500);
          response.end('Internal error: ' + error.toString());
        });
      }
    });
  }

}

function buildPagesHtml(pages: Array<Page>): string {
  const pagesHtml = pages.map((page) => {
    return escapeHtml`

          ${page.deviceName} / ${page.title}

    `;
  }).join('\n');

  return `

      <title>Pages</title>

Pages

          ${pagesHtml}

  `;
}

function escapeHtml(pieces: Array<string>, ...substitutions: Array<string>): string {
  let result = pieces[0];
  for (let i = 0; i < substitutions.length; ++i) {
    result += substitutions[i].replace(/[<&"'>]/g, escapeHtmlSpecialChar) + pieces[i + 1];
  }

  return result;
}

function escapeHtmlSpecialChar(char: string): string {
  return (
    char === '&' ? '&' :
    char === '"' ? '"' :
    char === "'" ? ''' :
    char === '<' ? '<' :
    char === '>' ? '>' :
    char
  );
}

function makeInspectorPageUrl(deviceId: string, pageId: string): string {
  // The inspector frontend doesn't handle urlencoded params so we
  // manually urlencode it and decode it on the other side in _createPageHandler
  const query = querystring.escape(`device=${deviceId}&page=${pageId}`);
  return `localhost:8081/inspector/page?${query}`;
}

function attachToServer(server: http.Server, pathPrefix: string): InspectorProxy {
  const proxy = new InspectorProxy();
  proxy.attachToServer(server, pathPrefix);
  return proxy;
}

if (!module.parent) {
  console.info('Starting server');
  process.env.DEBUG = 'RNP:Inspector';
  const serverInstance = http.createServer().listen(
    8081,
    'localhost',
    undefined,
    function() {
      attachToServer(serverInstance, '/inspector');
    }
  );
  serverInstance.timeout = 0;
}

// module.exports.attachToServer = attachToServer;
module.exports = InspectorProxy;
<|endoftext|>


<commit_before>'use strict'

const Wit = require('node-wit').Wit

const lastEntityValue = (entities, entity) => {
  let entityVal = entities && entities[entity]

  if (entityVal) {
    let val = Array.isArray(entityVal) &&
      entityVal.length > 0 &&
      entityVal[entityVal.length - 1].value

    if (val) {
      return typeof val === 'object' ? val.value : val
    }
  }

  return null
}

const actions = {
  say: (sessionId, msg, cb) => {
    cb()
  },

  merge: (context, entities, cb) => {
    delete context.response

    const location = lastEntityValue(entities, 'location')
    const intent = lastEntityValue(entities, 'intent')

    // what should be done if location not recognised?
    if (location) {
      context.location = location
    }

    if (intent) {
      context.intent = intent
    }

    cb(context)
  },

  error: (sessionId, msg) => {
    console.log('Oops, I don\'t know what to do.', msg)
  },

  fetchObjectsByLocation: (context, cb) => {
    // console.log('debug object fetcher', context.intent)
    // Here should go the api call, e.g.:
    // context.forecast = apiCall(context.location)
    switch (context.intent) {
    case 'tours':
      context.response = '2 hour tour, 20 POI tour, Super nice tour'
      break
    case 'museums':
      context.response = 'Super cool museum, other extra cool museum and some shitty one'
      break
    default:
      console.log('nope')
    }
    cb(context)
  },

  cleanupSessionContext: (context, cb) => {
    delete context.intent
    delete context.location
    delete context.response

    cb()
  }
}

const makeClient = function (token, specificActions) {
  console.log(specificActions)

  return new Wit(token, Object.assign(actions, specificActions))
}

module.exports = makeClient

<commit_msg>debug<commit_after>'use strict'

const Wit = require('node-wit').Wit

const lastEntityValue = (entities, entity) => {
  let entityVal = entities && entities[entity]

  if (entityVal) {
    let val = Array.isArray(entityVal) &&
      entityVal.length > 0 &&
      entityVal[entityVal.length - 1].value

    if (val) {
      return typeof val === 'object' ? val.value : val
    }
  }

  return null
}

const actions = {
  say: (sessionId, msg, cb) => {
    cb()
  },

  merge: (context, entities, cb) => {
    delete context.response

    const location = lastEntityValue(entities, 'location')
    const intent = lastEntityValue(entities, 'intent')

    // what should be done if location not recognised?
    if (location) {
      context.location = location
    }

    if (intent) {
      context.intent = intent
    }

    cb(context)
  },

  error: (sessionId, msg) => {
    console.log('Oops, I don\'t know what to do.', msg)
  },

  fetchObjectsByLocation: (context, cb) => {
    // console.log('debug object fetcher', context.intent)
    // Here should go the api call, e.g.:
    // context.forecast = apiCall(context.location)
    switch (context.intent) {
    case 'tours':
      context.response = '2 hour tour, 20 POI tour, Super nice tour'
      break
    case 'museums':
      context.response = 'Super cool museum, other extra cool museum and some shitty one'
      break
    default:
      console.log('nope')
    }
    cb(context)
  },

  cleanupSessionContext: (context, cb) => {
    delete context.intent
    delete context.location
    delete context.response

    cb()
  }
}

const makeClient = function (token, specificActions) {
  return new Wit(token, Object.assign(actions, specificActions))
}

module.exports = makeClient

<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import { TEMPERATURE_SYNC_ACTIONS } from '../actions/TemperatureSyncActions';

const TEMPERATURE_SYNC_STATES = {
  SCANNING: 'SCANNING',
  SCAN_ERROR: 'SCAN_ERROR',
};

const initialState = () => ({
  progress: 0,
  total: 0,
  syncState: null,
  isSyncing: false,
});

export const TemperatureSyncReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case TEMPERATURE_SYNC_ACTIONS.START_SCAN: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.SCANNING };
    }
    case TEMPERATURE_SYNC_ACTIONS.COMPLETE_SCAN: {
      return { ...state, syncState: null };
    }
    case TEMPERATURE_SYNC_ACTIONS.SCAN_ERROR: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.SCAN_ERROR };
    }

    default:
      return state;
  }
};
<commit_msg>Add downloading logs cases to TemperatureSyncReducer<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import { TEMPERATURE_SYNC_ACTIONS } from '../actions/TemperatureSyncActions';

const TEMPERATURE_SYNC_STATES = {
  SCANNING: 'SCANNING',
  SCAN_ERROR: 'SCAN_ERROR',
  DOWNLOADING_LOGS: 'DOWNLOADING_LOGS',
  DOWNLOADING_LOGS_ERROR: 'DOWNLOADING_LOGS_ERROR',
};

const initialState = () => ({
  progress: 0,
  total: 0,
  syncState: null,
  isSyncing: false,
});

export const TemperatureSyncReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case TEMPERATURE_SYNC_ACTIONS.START_SCAN: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.SCANNING };
    }
    case TEMPERATURE_SYNC_ACTIONS.COMPLETE_SCAN: {
      return { ...state, syncState: null };
    }
    case TEMPERATURE_SYNC_ACTIONS.SCAN_ERROR: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.SCAN_ERROR };
    }

    case TEMPERATURE_SYNC_ACTIONS.DOWNLOAD_LOGS_START: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.DOWNLOADING_LOGS };
    }
    case TEMPERATURE_SYNC_ACTIONS.DOWNLOAD_LOGS_ERROR: {
      return { ...state, syncState: TEMPERATURE_SYNC_STATES.DOWNLOADING_LOGS_ERROR };
    }
    case TEMPERATURE_SYNC_ACTIONS.DOWNLOAD_LOGS_COMPLETE: {
      return { ...state, syncState: null };
    }

    default:
      return state;
  }
};
<|endoftext|>


<commit_before>import {
    bindable,
    containerless
} from 'aurelia-framework';

@containerless
export class EmChatTodo {

    @bindable actived;
    loginUser = nsCtx.loginUser;
    title;
    todos = [];
    dones = [];
    todoFilter = '';

    last = true;

    activedChanged(newValue, oldValue) {
        if (!newValue || !this.actived || this.actived.payload.action != nsCons.ACTION_TYPE_TODO) {
            return;
        }

        this.listMy();
    }

    init() {
        if (!this.actived) {
            this.actived = {
                show: 'todo',
                payload: {
                    action: nsCons.ACTION_TYPE_TODO
                }
            };
        }
    }

    refresh() {
        this.init();
        this.listMy();
    }

    listMy() {

        this.ajax = $.get('/admin/todo/listMy/undone', {}, (data) => {
            if (data.success) {
                this.todos = data.data;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });

        this.ajaxDone = $.get('/admin/todo/listMy/done', {
            size: 20,
            page: 0,
        }, (data) => {
            if (data.success) {

                this.dones = data.data.content;

                this.page = data.data;
                this.last = data.data.last;
                this.moreCnt = data.data.totalElements - (data.data.number + 1) * data.data.size;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });

    }

    attached() {
        $(this.tasksAccRef).accordion({
            exclusive: false
        });
    }

    addTodoHandler(ctrlKey) {

        $(this.todoInputRef).focus();

        if (!this.title) {
            toastr.error('请输入待办事项内容！');
            return;
        }

        let topTodo = ctrlKey || (event && (event.ctrlKey || event.metaKey));

        this.ajax = $.post('/admin/todo/create', {
            title: this.title
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.title = '';
                this.todos = [data.data, ...this.todos];

                if (topTodo) {
                    this.topHandler(data.data);
                }
            } else {
                toastr.error(data.data, '创建待办事项失败！');
            }
        });
    }

    addTodoKeyupHandler(event) {

        if (event.keyCode == 13) {
            this.addTodoHandler(event.ctrlKey);
        }
    }

    statusToggleHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: item.status == 'New' ? 'Doing' : "New"
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.status = data.data.status;
                item.updateDate = data.data.updateDate;
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    priorityUpdateHandler(item, priority) {
        // Default, ZyJj, ZyBjj, BzyJi, BzyBjj;
        $.post('/admin/todo/update', {
            id: item.id,
            priority: priority ? priority : 'Default'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.priority = data.data.priority;
                item.updateDate = data.data.updateDate;
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    statusDoneHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: 'Done'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.todos = _.reject(this.todos, {
                    id: item.id
                });
                this.dones = [data.data, ...this.dones];
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    statusNewHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: 'New'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.dones = _.reject(this.dones, {
                    id: item.id
                });
                this.todos = [data.data, ...this.todos];
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    delHandler(item, type) {
        $.post(`/admin/todo/delete/${item.id}`, {}, (data, textStatus, xhr) => {
            if (data.success) {
                if (type == 'todo') {
                    this.todos = _.reject(this.todos, {
                        id: item.id
                    });
                } else {
                    this.dones = _.reject(this.dones, {
                        id: item.id
                    });
                }
            } else {
                toastr.error(data.data, '删除待办事项失败！');
            }
        });
    }

    editHandler(item, inputRef) {
        item.isEditing = true;
        item.oldTitle = item.title;
        _.defer(() => $(inputRef).focus());
    }

    editContentHandler(item, textareaRef) {
        item.isOpen = !item.isOpen;
        if (item.isOpen) {
            item.oldContent = item.content;
            _.defer(() => {
                $(textareaRef).focus()
                if (item.content) {
                    autosize.update(textareaRef);
                }
            });
        }
    }

    updateHandler(item) {
        item.isEditing = false;
        if (!_.trim(item.title) || item.title == item.oldTitle) {
            item.title = item.oldTitle
            return;
        }
        $.post('/admin/todo/update', {
            id: item.id,
            title: item.title
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.oldTitle = data.data.title;
                toastr.success('更新待办事项内容成功！');
            } else {
                item.title = item.oldTitle;
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    updateDescHandler(item) {
        if (item.content == item.oldContent) {
            return;
        }
        $.post('/admin/todo/update', {
            id: item.id,
            content: item.content
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.oldContent = data.data.content;
                toastr.success('更新待办事项描述成功！');
            } else {
                item.content = item.oldContent;
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    topHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            sortIndex: item.sortIndex ? 0 : 1,
            status: !item.sortIndex ? 'Doing' : 'New'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.sortIndex = data.data.sortIndex;
                item.status = data.data.status;
                toastr.success(`${item.sortIndex ? '' : '取消'}置顶待办事项成功！`);
                this.todos = [...this.todos];
            } else {
                toastr.error(data.data, `${!item.sortIndex ? '' : '取消'}置顶待办事项失败！`);
            }
        });
    }

    searchFocusHandler() {
        $(this.searchRemoveRef).show();
    }

    searchBlurHandler() {
        if (!this.todoFilter) {
            $(this.searchRemoveRef).hide();
        }
    }

    clearSearchHandler() {
        this.todoFilter = '';
        $(this.searchInputRef).focus();
    }

    loadMoreHandler() {
        this.searchMoreP = $.get('/admin/todo/listMy/done', {
            size: this.page.size,
            page: this.page.number + 1
        }, (data) => {
            if (data.success) {

                this.dones = [...this.dones, ...data.data.content];

                this.page = data.data;
                this.last = data.data.last;
                this.moreCnt = data.data.totalElements - (data.data.number + 1) * data.data.size;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });
    }

}
<commit_msg>Update em-chat-todo.js<commit_after>import {
    bindable,
    containerless
} from 'aurelia-framework';

@containerless
export class EmChatTodo {

    @bindable actived;
    loginUser = nsCtx.loginUser;
    title;
    todos = [];
    dones = [];
    todoFilter = '';

    last = true;

    activedChanged(newValue, oldValue) {
        if (!newValue || !this.actived || this.actived.payload.action != nsCons.ACTION_TYPE_TODO) {
            return;
        }

        this.listMy();
    }

    init() {
        if (!this.actived) {
            this.actived = {
                show: 'todo',
                payload: {
                    action: nsCons.ACTION_TYPE_TODO
                }
            };
        }
    }

    refresh() {
        this.init();
        this.listMy();
    }

    listMy() {

        this.ajax = $.get('/admin/todo/listMy/undone', {}, (data) => {
            if (data.success) {
                this.todos = data.data;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });

        this.ajaxDone = $.get('/admin/todo/listMy/done', {
            size: 20,
            page: 0,
        }, (data) => {
            if (data.success) {

                this.dones = data.data.content;

                this.page = data.data;
                this.last = data.data.last;
                this.moreCnt = data.data.totalElements - (data.data.number + 1) * data.data.size;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });

    }

    attached() {
        $(this.tasksAccRef).accordion({
            exclusive: false
        });
    }

    addTodoHandler(ctrlKey) {

        $(this.todoInputRef).focus();

        if (!this.title) {
            toastr.error('请输入待办事项内容！');
            return;
        }

        let topTodo = ctrlKey || (event && (event.ctrlKey || event.metaKey));

        this.ajax = $.post('/admin/todo/create', {
            title: this.title
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.title = '';
                this.todos = [data.data, ...this.todos];

                if (topTodo) {
                    // this.topHandler(data.data);
                    this.priorityUpdateHandler(data.data, 'ZyJj', () => this.topHandler(data.data));
                }
            } else {
                toastr.error(data.data, '创建待办事项失败！');
            }
        });
    }

    addTodoKeyupHandler(event) {

        if (event.keyCode == 13) {
            this.addTodoHandler(event.ctrlKey);
        }
    }

    statusToggleHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: item.status == 'New' ? 'Doing' : "New"
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.status = data.data.status;
                item.updateDate = data.data.updateDate;
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    priorityUpdateHandler(item, priority, callback) {
        // Default, ZyJj, ZyBjj, BzyJi, BzyBjj;
        $.post('/admin/todo/update', {
            id: item.id,
            priority: priority ? priority : 'Default'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.priority = data.data.priority;
                item.updateDate = data.data.updateDate;
                callback && callback();
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    statusDoneHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: 'Done'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.todos = _.reject(this.todos, {
                    id: item.id
                });
                this.dones = [data.data, ...this.dones];
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    statusNewHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            status: 'New'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                this.dones = _.reject(this.dones, {
                    id: item.id
                });
                this.todos = [data.data, ...this.todos];
            } else {
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    delHandler(item, type) {
        $.post(`/admin/todo/delete/${item.id}`, {}, (data, textStatus, xhr) => {
            if (data.success) {
                if (type == 'todo') {
                    this.todos = _.reject(this.todos, {
                        id: item.id
                    });
                } else {
                    this.dones = _.reject(this.dones, {
                        id: item.id
                    });
                }
            } else {
                toastr.error(data.data, '删除待办事项失败！');
            }
        });
    }

    editHandler(item, inputRef) {
        item.isEditing = true;
        item.oldTitle = item.title;
        _.defer(() => $(inputRef).focus());
    }

    editContentHandler(item, textareaRef) {
        item.isOpen = !item.isOpen;
        if (item.isOpen) {
            item.oldContent = item.content;
            _.defer(() => {
                $(textareaRef).focus()
                if (item.content) {
                    autosize.update(textareaRef);
                }
            });
        }
    }

    updateHandler(item) {
        item.isEditing = false;
        if (!_.trim(item.title) || item.title == item.oldTitle) {
            item.title = item.oldTitle
            return;
        }
        $.post('/admin/todo/update', {
            id: item.id,
            title: item.title
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.oldTitle = data.data.title;
                toastr.success('更新待办事项内容成功！');
            } else {
                item.title = item.oldTitle;
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    updateDescHandler(item) {
        if (item.content == item.oldContent) {
            return;
        }
        $.post('/admin/todo/update', {
            id: item.id,
            content: item.content
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.oldContent = data.data.content;
                toastr.success('更新待办事项描述成功！');
            } else {
                item.content = item.oldContent;
                toastr.error(data.data, '更新待办事项失败！');
            }
        });
    }

    topHandler(item) {
        $.post('/admin/todo/update', {
            id: item.id,
            sortIndex: item.sortIndex ? 0 : 1,
            status: !item.sortIndex ? 'Doing' : 'New'
        }, (data, textStatus, xhr) => {
            if (data.success) {
                item.updateDate = data.data.updateDate;
                item.sortIndex = data.data.sortIndex;
                item.status = data.data.status;
                toastr.success(`${item.sortIndex ? '' : '取消'}置顶待办事项成功！`);
                this.todos = [...this.todos];
            } else {
                toastr.error(data.data, `${!item.sortIndex ? '' : '取消'}置顶待办事项失败！`);
            }
        });
    }

    searchFocusHandler() {
        $(this.searchRemoveRef).show();
    }

    searchBlurHandler() {
        if (!this.todoFilter) {
            $(this.searchRemoveRef).hide();
        }
    }

    clearSearchHandler() {
        this.todoFilter = '';
        $(this.searchInputRef).focus();
    }

    loadMoreHandler() {
        this.searchMoreP = $.get('/admin/todo/listMy/done', {
            size: this.page.size,
            page: this.page.number + 1
        }, (data) => {
            if (data.success) {

                this.dones = [...this.dones, ...data.data.content];

                this.page = data.data;
                this.last = data.data.last;
                this.moreCnt = data.data.totalElements - (data.data.number + 1) * data.data.size;
            } else {
                toastr.error(data.data, '获取待办事项列表失败！');
            }
        });
    }

}
<|endoftext|>


<commit_before>/** Class: BoxLayout
 *
 * Inherits from:
 *     - <LayoutManager>
 */
define(function(require, exports, module) {

	var LayoutManager = require('./LayoutManager');

	/** Constructor: Label(text)
	 * Creates a new instance of <Label>.
	 *
	 * Parameters:
	 *     (String) text - Text to show on the Label (optional)
	 */
	var BoxLayout = function BoxLayout() {
		var _self = this;

		LayoutManager.call(this);

		/** Function: layoutWidgets(parent, widgets)
		 * Takes an array of <Widget>s and positions them inside parents bounds.
		 *
		 * Parameters:
		 *     (Widget) parent
		 *     (Array) widgtes - An Array containing parents children
		 */
		_self.layoutWidgets = function layoutWidgets(parent, widgets) {
			var size = parent.getSize();
			var left = 0;

			for(var i=0, l=widgets.length; i<l; i++) {
				widgets[i].setPosition(left,0);
				left += widgets[i].getSize().width;
			}

		}

	};

	BoxLayout.prototype = Object.create(LayoutManager.prototype);
	BoxLayout.prototype.constructor = BoxLayout;

	return BoxLayout;
});<commit_msg>implemented horizontal placement for boxlayout<commit_after>/** Class: BoxLayout
 *
 * Inherits from:
 *     - <LayoutManager>
 */
define(function(require, exports, module) {

	var LayoutManager = require('./LayoutManager');

	/** Constructor: BoxLayout()
	 * Creates a new instance of <BoxLayout>
	 */
	var BoxLayout = function BoxLayout() {
		var _self = this;

		LayoutManager.call(this);

		/** Function: layoutWidgets(parent, widgets)
		 *
		 *
		 * Parameters:
		 *     (Widget) parent
		 *     (Array) widgtes - An Array containing parents children
		 */
		_self.layoutWidgets = function layoutWidgets(parent, widgets) {
			var size = parent.getSize();
			var left = 0;

			for(var i=0, l=widgets.length; i<l; i++) {
				widgets[i].setPosition(left,0);
				left += widgets[i].getSize().width;
			}

		}

	};

	BoxLayout.prototype = Object.create(LayoutManager.prototype);
	BoxLayout.prototype.constructor = BoxLayout;

	return BoxLayout;
});<|endoftext|>


<commit_before>import React from 'react';
import Navigation from './Navigation';
import Loader from 'components/Loader';
import Alert from 'components/Alert';
import ViewTOS from './ViewTOS';
import './Homeview.scss';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

export class HomeView extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      showAlert: this.props.message.active
    };
  }
  componentWillMount () {
    this.props.fetchAttributeTypes();
    this.props.fetchRecordTypes();
  }
  componentWillReceiveProps (nextProps) {
    if (nextProps.message) {
      this.setState({ showAlert: nextProps.message.active });
    }
  }
  render () {
    const {
      fetchNavigation,
      setNavigationVisibility,
      navigation,
      fetchTOS,
      selectedTOS,
      phases,
      actions,
      records,
      selectedTOSPath,
      setPhaseVisibility,
      isFetching,
      setPhasesVisibility,
      documentState,
      setDocumentState,
      recordTypes,
      attributeTypes,
      addAction,
      addRecord,
      addPhase,
      changeOrder,
      importItems,
      message
    } = this.props;
    let alertMessage = null;
    if (this.state.showAlert === true) {
      alertMessage =
        <Alert
          message={message.text}
          style={(message.success ? 'alert-success' : 'alert-danger')}
          close={this.props.closeMessage}
        />;
      setTimeout(this.props.closeMessage, 6000);
    }
    return (

        { isFetching &&
          <Loader
            isFetching={isFetching}
          />
        }
        <Navigation
          fetchTOS={fetchTOS}
          fetchNavigation={fetchNavigation}
          navigation={navigation}
          setNavigationVisibility={setNavigationVisibility}
          selectedTOSPath={selectedTOSPath}
        />
        <ViewTOS
          selectedTOS={selectedTOS}
          phases={phases}
          actions={actions}
          records={records}
          setPhaseVisibility={setPhaseVisibility}
          isFetching={isFetching}
          setPhasesVisibility={setPhasesVisibility}
          documentState={documentState}
          setDocumentState={setDocumentState}
          recordTypes={recordTypes}
          attributeTypes={attributeTypes}
          addAction={addAction}
          addRecord={addRecord}
          addPhase={addPhase}
          changeOrder={changeOrder}
          importItems={importItems}
        />
        <ReactCSSTransitionGroup
          transitionName={'alert-position'}
          transitionEnterTimeout={1000}
          transitionLeaveTimeout={600}>
          { this.state.showAlert &&
            alertMessage
          }
        </ReactCSSTransitionGroup>

    );
  }
};
HomeView.propTypes = {
  phases: React.PropTypes.object.isRequired,
  actions: React.PropTypes.object.isRequired,
  records: React.PropTypes.object.isRequired,
  fetchTOS: React.PropTypes.func.isRequired,
  fetchNavigation: React.PropTypes.func.isRequired,
  navigation: React.PropTypes.object.isRequired,
  setNavigationVisibility: React.PropTypes.func.isRequired,
  selectedTOS: React.PropTypes.object.isRequired,
  selectedTOSPath: React.PropTypes.array.isRequired,
  setPhaseVisibility: React.PropTypes.func.isRequired,
  isFetching: React.PropTypes.bool.isRequired,
  setPhasesVisibility: React.PropTypes.func.isRequired,
  documentState: React.PropTypes.string.isRequired,
  setDocumentState: React.PropTypes.func.isRequired,
  fetchRecordTypes: React.PropTypes.func.isRequired,
  recordTypes: React.PropTypes.object.isRequired,
  attributeTypes: React.PropTypes.object.isRequired,
  addAction: React.PropTypes.func.isRequired,
  addRecord: React.PropTypes.func.isRequired,
  addPhase: React.PropTypes.func.isRequired,
  fetchAttributeTypes: React.PropTypes.func.isRequired,
  changeOrder: React.PropTypes.func.isRequired,
  importItems: React.PropTypes.func.isRequired,
  message: React.PropTypes.object.isRequired,
  closeMessage: React.PropTypes.func.isRequired
};
export default HomeView;
<commit_msg>Fix lowercase issue<commit_after>import React from 'react';
import Navigation from './Navigation';
import Loader from 'components/Loader';
import Alert from 'components/Alert';
import ViewTOS from './ViewTOS';
import './HomeView.scss';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

export class HomeView extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      showAlert: this.props.message.active
    };
  }
  componentWillMount () {
    this.props.fetchAttributeTypes();
    this.props.fetchRecordTypes();
  }
  componentWillReceiveProps (nextProps) {
    if (nextProps.message) {
      this.setState({ showAlert: nextProps.message.active });
    }
  }
  render () {
    const {
      fetchNavigation,
      setNavigationVisibility,
      navigation,
      fetchTOS,
      selectedTOS,
      phases,
      actions,
      records,
      selectedTOSPath,
      setPhaseVisibility,
      isFetching,
      setPhasesVisibility,
      documentState,
      setDocumentState,
      recordTypes,
      attributeTypes,
      addAction,
      addRecord,
      addPhase,
      changeOrder,
      importItems,
      message
    } = this.props;
    let alertMessage = null;
    if (this.state.showAlert === true) {
      alertMessage =
        <Alert
          message={message.text}
          style={(message.success ? 'alert-success' : 'alert-danger')}
          close={this.props.closeMessage}
        />;
      setTimeout(this.props.closeMessage, 6000);
    }
    return (

        { isFetching &&
          <Loader
            isFetching={isFetching}
          />
        }
        <Navigation
          fetchTOS={fetchTOS}
          fetchNavigation={fetchNavigation}
          navigation={navigation}
          setNavigationVisibility={setNavigationVisibility}
          selectedTOSPath={selectedTOSPath}
        />
        <ViewTOS
          selectedTOS={selectedTOS}
          phases={phases}
          actions={actions}
          records={records}
          setPhaseVisibility={setPhaseVisibility}
          isFetching={isFetching}
          setPhasesVisibility={setPhasesVisibility}
          documentState={documentState}
          setDocumentState={setDocumentState}
          recordTypes={recordTypes}
          attributeTypes={attributeTypes}
          addAction={addAction}
          addRecord={addRecord}
          addPhase={addPhase}
          changeOrder={changeOrder}
          importItems={importItems}
        />
        <ReactCSSTransitionGroup
          transitionName={'alert-position'}
          transitionEnterTimeout={1000}
          transitionLeaveTimeout={600}>
          { this.state.showAlert &&
            alertMessage
          }
        </ReactCSSTransitionGroup>

    );
  }
};
HomeView.propTypes = {
  phases: React.PropTypes.object.isRequired,
  actions: React.PropTypes.object.isRequired,
  records: React.PropTypes.object.isRequired,
  fetchTOS: React.PropTypes.func.isRequired,
  fetchNavigation: React.PropTypes.func.isRequired,
  navigation: React.PropTypes.object.isRequired,
  setNavigationVisibility: React.PropTypes.func.isRequired,
  selectedTOS: React.PropTypes.object.isRequired,
  selectedTOSPath: React.PropTypes.array.isRequired,
  setPhaseVisibility: React.PropTypes.func.isRequired,
  isFetching: React.PropTypes.bool.isRequired,
  setPhasesVisibility: React.PropTypes.func.isRequired,
  documentState: React.PropTypes.string.isRequired,
  setDocumentState: React.PropTypes.func.isRequired,
  fetchRecordTypes: React.PropTypes.func.isRequired,
  recordTypes: React.PropTypes.object.isRequired,
  attributeTypes: React.PropTypes.object.isRequired,
  addAction: React.PropTypes.func.isRequired,
  addRecord: React.PropTypes.func.isRequired,
  addPhase: React.PropTypes.func.isRequired,
  fetchAttributeTypes: React.PropTypes.func.isRequired,
  changeOrder: React.PropTypes.func.isRequired,
  importItems: React.PropTypes.func.isRequired,
  message: React.PropTypes.object.isRequired,
  closeMessage: React.PropTypes.func.isRequired
};
export default HomeView;
<|endoftext|>