<commit_before>'use strict';

/* global Util, DOM */

const itype = require('itype/legacy');
const Emitify = require('emitify/legacy');
const inherits = require('inherits');
const rendy = require('rendy/legacy');
const exec = require('execon');
const Images = require('./dom/images');
const registerSW = require('./sw/register');
const join = require('join-io/www/join');
const jonny = require('jonny/legacy');
const currify = require('currify/legacy');

const bind = (f, ...a) => () => f(...a);
const noop = () => {};

const {
    apiURL,
    formatMsg,
    buildFromJSON,
} = require('../common/cloudfunc');

/* global Util, DOM */

inherits(CloudCmdProto, Emitify);

module.exports = new CloudCmdProto(Util, DOM);

function CloudCmdProto(Util, DOM) {
    let Key;
    let Debug;
    let Listeners;

    const log = (str) => {
        if (!Debug)
            return;

        console.log(str);
    };

    registerSW();

    Emitify.call(this);

    const CloudCmd = this;
    const Info = DOM.CurrentInfo;
    const Storage = DOM.Storage;
    const Files = DOM.Files;

    this.log = log;
    this.PREFIX = '';
    this.PREFIX_URL = '';
    this.DIRCLIENT = '/dist/';
    this.DIRCLIENT_MODULES = this.DIRCLIENT + 'modules/';

    this.MIN_ONE_PANEL_WIDTH    = 1155;
    this.HOST                   = location.origin ||
                                  location.protocol + '//' + location.host;

    const TITLE = 'Cloud Commander';
    this.TITLE = TITLE;

    this.sort = {
        left: 'name',
        right: 'name',
    };

    this.order = {
        left: 'asc',
        right: 'asc',
    };

    log.enable = () => {
        Debug = true;
    };

    log.disable = () => {
        Debug = false;
    };

    const kebabToCamelCase = Util.kebabToCamelCase;

    /**
     * Функция привязываеться ко всем ссылкам и
     *  загружает содержимое каталогов
     *
     * @param params - {
     *          paramLink - ссылка
     *          needRefresh - необходимость обязательной загрузки данных с сервера
     *          panel
     *      }
     * @param callback
     */
    this.loadDir = (params, callback) => {
        const p = params;

        const refresh = p.isRefresh;
        const panel = p.panel;
        const history = p.history;
        const noCurrent = p.noCurrent;
        const currentName = p.currentName;

        let panelChanged;
        if (!noCurrent)
            if (panel && panel !== Info.panel) {
                DOM.changePanel();
                panelChanged = true;
            }

        let imgPosition;
        if (panelChanged || refresh || !history)
            imgPosition = 'top';

        Images.show.load(imgPosition, panel);

        /* загружаем содержимое каталога */
        ajaxLoad(p.path, {
            refresh,
            history,
            noCurrent,
            currentName,
        }, panel, callback);
    };

    /**
     * function load modules
     * @params = {name, path, func, dobefore, arg}
     */
    function loadModule(params) {
        if (!params)
            return;

        let path = params.path;
        const name = params.name || path && kebabToCamelCase(path);
        const func = params.func;
        const funcName = params.funcName;
        const doBefore = params.dobefore;

        const isContain = /\.js/.test(path);

        if (!isContain)
            path += '.js';

        if (CloudCmd[name])
            return;

        CloudCmd[name] = (...args) => {
            const prefix = CloudCmd.PREFIX;
            const pathFull = prefix + CloudCmd.DIRCLIENT_MODULES + path;

            exec(doBefore);

            const done = (error) => {
                const Proto = CloudCmd[name];

                if (error || !itype.function(Proto))
                    return;

                CloudCmd[name] = applyConstructor(Proto, args);
            };

            return DOM.load.js(pathFull, func || done);
        };

        CloudCmd[name][funcName] = CloudCmd[name];
    }

    /*
     * apply arguemnts to constructor
     *
     * @param constructor
     * @param args
     */
    function applyConstructor(constructor, args) {
        const F = function () {
            return constructor.apply(this, args);
        };

        F.prototype = constructor.prototype;
        return new F();
    }

    /**
     * Конструктор CloudClient, который
     * выполняет весь функционал по
     * инициализации
     */
    this.init = (prefix, config) => {
        const func = bind(exec.parallel, [
            initModules,
            baseInit,
            loadPlugins,
            loadStyle,
            exec.with(CloudCmd.route, location.hash),
        ], noop);

        const funcBefore  = (callback) => {
            const src = prefix + '/join:' + [
                CloudCmd.DIRCLIENT_MODULES + 'polyfill.js',
                '/modules/domtokenlist-shim/dist/domtokenlist.min.js',
            ].join(':');

            DOM.loadJquery(() => {
                DOM.load.js(src, callback);
            });
        };

        CloudCmd.PREFIX = prefix;
        CloudCmd.PREFIX_URL = prefix + apiURL;

        CloudCmd.config = (key) => config[key];
        CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
        CloudCmd._config = (key, value) => {
            /*
             * should be called from config.js only
             * after successful update on server
             */

            if (key === 'password')
                return;

            config[key] = value;
        };

        if (config.oneFilePanel)
            CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;

        exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore);
    };

    function loadStyle(callback) {
        const prefix = CloudCmd.PREFIX;
        const plugins = prefix + '/dist/cloudcmd.common.css';

        DOM.load.css(plugins, callback);
    }

    function loadPlugins(callback) {
        const prefix = CloudCmd.PREFIX;
        const plugins = prefix + '/plugins.js';

        DOM.load.js(plugins, callback);
    }

    this.join = (urls) => {
        const prefix  = CloudCmd.PREFIX;

        if (!Array.isArray(urls))
            throw Error('urls should be array!');

        const noPrefixUrls = urls.map((url) => {
            return url.replace(prefix, '');
        });

        return prefix + join(noPrefixUrls);
    };

    this.route = (path) => {
        const query = path.split('/');

        if (!path)
            return;

        const [kebabModule] = query;
        const module = kebabToCamelCase(kebabModule.slice(1));

        const file = query[1];
        const current = DOM.getCurrentByName(file);

        if (file && !current) {
            const msg = formatMsg('set current file', file, 'error');
            CloudCmd.log(msg);
            return;
        }

        DOM.setCurrentFile(current);

        CloudCmd.execFromModule(module, 'show');
    };

    this.logOut = () => {
        const url = CloudCmd.PREFIX + '/logout';
        const error = () => document.location.reload();

        DOM.load.ajax({url, error});
    };

    function initModules(callback) {
        exec.if(CloudCmd.Key, () => {
            Key = new CloudCmd.Key();
            CloudCmd.Key = Key;
            Key.bind();
        }, (func) => {
            /* привязываем клавиши к функциям */
            const path = 'key.js';

            loadModule({
                path,
                func
            });
        });

        Files.get('modules', (error, modules) => {
            const showLoad = Images.show.load;

            const doBefore = {
                'edit': showLoad,
                'menu': showLoad,
            };

            const load = (name, path, dobefore) => {
                const isTmpl = path === 'template';
                const funcName = isTmpl ? 'get' : 'show';

                loadModule({
                    name,
                    path,
                    dobefore,
                    funcName,
                });
            };

            if (!modules)
                modules = [];

            modules.local.forEach((module) => {
                load(null, module, doBefore[module]);
            });

            callback();
        });
    }

    function baseInit(callback) {
        const files = DOM.getFiles();

        CloudCmd.on('current-file', DOM.updateCurrentInfo);

        /* выделяем строку с первым файлом */
        if (files)
            DOM.setCurrentFile(files[0], {
                // when hash is present
                // it should be handled with this.route
                // overwre otherwise
                history: !location.hash
            });

        const dirPath = DOM.getCurrentDirPath();
        Listeners = CloudCmd.Listeners;
        Listeners.init();

        const panels = getPanels();
        panels.forEach(Listeners.setOnPanel);

        Listeners.initKeysPanel();

        if (!CloudCmd.config('dirStorage'))
            return callback();

        Storage.get(dirPath, (error, data) => {
            if (!data) {
                data = getJSONfromFileTable();
                Storage.set(dirPath, data);
            }
            callback();
        });
    }

    function getPanels() {
        const panels = ['left'];

        if (CloudCmd.config('oneFilePanel'))
            return panels;

        return [
            ...panels,
            'right',
        ];
    }

    this.execFromModule = (moduleName, funcName, ...args) => {
        const obj = CloudCmd[moduleName];
        const isObj = itype.object(obj);

        exec.if(isObj, () => {
            const obj = CloudCmd[moduleName];
            const func = obj[funcName];

            func(...args);
        }, obj);
    };

    this.refresh = (options = {}, callback) => {
        if (!callback && typeof options === 'function') {
            callback = options;
            options = {};
        }

        const panel = options.panel || Info.panel;
        const path = DOM.getCurrentDirPath(panel);

        const isRefresh = true;
        const history = false;
        const noCurrent = options ? options.noCurrent : false;
        const currentName = options.currentName;

        CloudCmd.loadDir({
            path,
            isRefresh,
            history,
            panel,
            noCurrent,
            currentName,
        }, callback);
    };

    /**
     * Функция загружает json-данные о Файловой Системе
     * через ajax-запрос.
     * @param path - каталог для чтения
     * @param options
     * { refresh, history } - необходимость обновить данные о каталоге
     * @param panel
     * @param callback
     *
     */
    function ajaxLoad(path, options, panel, callback) {
        const create = (error, json) => {
            const RESTful = DOM.RESTful;
            const name = options.currentName || Info.name;
            const obj = jonny.parse(json);
            const isRefresh = options.refresh;
            const noCurrent = options.noCurrent;

            if (!isRefresh && json)
                return createFileTable(obj, panel, options, callback);

            const position = DOM.getPanelPosition(panel);
            const sort = CloudCmd.sort[position];
            const order = CloudCmd.order[position];

            const query = rendy('?sort={{ sort }}&order={{ order }}', {
                sort,
                order,
            });

            RESTful.read(path + query, 'json', (error, obj) => {
                if (error)
                    return;

                options.sort = sort;
                options.order = order;

                createFileTable(obj, panel, options, () => {
                    if (isRefresh && !noCurrent)
                        DOM.setCurrentByName(name);

                    exec(callback);
                });

                if (!CloudCmd.config('dirStorage'))
                    return;

                Storage.set(path, obj);
            });
        };

        if (!options)
            options    = {};

        CloudCmd.log('reading dir: "' + path + '";');

        if (!CloudCmd.config('dirStorage'))
            return create();

        Storage.get(path, create);
    }

    /**
     * Функция строит файловую таблицу
     * @param json  - данные о файлах
     * @param panelParam
     * @param history
     * @param callback
     */
    function createFileTable(json, panelParam, options, callback) {
        const {
            history,
            noCurrent,
        } = options;

        const names = ['file', 'path', 'link', 'pathLink'];

        Files.get(names, (error, templFile, templPath, templLink, templPathLink) => {
            const Dialog = DOM.Dialog;
            const panel = panelParam || DOM.getPanel();

            const {
                dir,
                name,
            } = Info;

            if (error)
                return Dialog.alert(TITLE, error.responseText);

            const childNodes = panel.childNodes;
            let i = childNodes.length;

            while (i--)
                panel.removeChild(panel.lastChild);

            panel.innerHTML = buildFromJSON({
                sort        : options.sort,
                order       : options.order,
                data        : json,
                id          : panel.id,
                prefix      : CloudCmd.PREFIX,
                template    : {
                    file        : templFile,
                    path        : templPath,
                    pathLink    : templPathLink,
                    link        : templLink
                }
            });

            Listeners.setOnPanel(panel);

            if (!noCurrent) {
                let current;

                if (name === '..' && dir !== '/')
                    current = DOM.getCurrentByName(dir);

                if (!current)
                    current = DOM.getFiles(panel)[0];

                DOM.setCurrentFile(current, {
                    history: history
                });

                CloudCmd.emit('active-dir', Info.dirPath);
            }

            exec(callback);
        });
    }

    /**
     * Функция генерирует JSON из html-таблицы файлов и
     * используеться при первом заходе в корень
     */
    function getJSONfromFileTable() {
        const path = DOM.getCurrentDirPath();
        const infoFiles = Info.files || [];

        const notParent = (current) => {
            const name = DOM.getCurrentName(current);
            return name !== '..';
        };

        const parse = (current) => {
            const name = DOM.getCurrentName(current);
            const size = DOM.getCurrentSize(current);
            const owner = DOM.getCurrentOwner(current);
            const mode = DOM.getCurrentMode(current);
            const date = DOM.getCurrentDate(current);

            return {
                name,
                size,
                mode,
                owner,
                date,
            };
        };

        const files = infoFiles
            .filter(notParent)
            .map(parse);

        const fileTable = {
            path,
            files,
        };

        return fileTable;
    }

    this.goToParentDir = () => {
        const dir = Info.dir;
        const {
            dirPath,
            parentDirPath,
        } = Info;

        if (dirPath === parentDirPath)
            return;

        const path = parentDirPath;

        CloudCmd.loadDir({path}, () => {
            const {panel} = Info;
            const current = DOM.getCurrentByName(dir);
            const first = DOM.getFiles(panel)[0];

            DOM.setCurrentFile(current || first, {
                history
            });
        });
    };
}

<commit_msg>refactor(client) loadStyle: plugins -> name<commit_after>'use strict';

/* global Util, DOM */

const itype = require('itype/legacy');
const Emitify = require('emitify/legacy');
const inherits = require('inherits');
const rendy = require('rendy/legacy');
const exec = require('execon');
const Images = require('./dom/images');
const registerSW = require('./sw/register');
const join = require('join-io/www/join');
const jonny = require('jonny/legacy');
const currify = require('currify/legacy');

const bind = (f, ...a) => () => f(...a);
const noop = () => {};

const {
    apiURL,
    formatMsg,
    buildFromJSON,
} = require('../common/cloudfunc');

/* global Util, DOM */

inherits(CloudCmdProto, Emitify);

module.exports = new CloudCmdProto(Util, DOM);

function CloudCmdProto(Util, DOM) {
    let Key;
    let Debug;
    let Listeners;

    const log = (str) => {
        if (!Debug)
            return;

        console.log(str);
    };

    registerSW();

    Emitify.call(this);

    const CloudCmd = this;
    const Info = DOM.CurrentInfo;
    const Storage = DOM.Storage;
    const Files = DOM.Files;

    this.log = log;
    this.PREFIX = '';
    this.PREFIX_URL = '';
    this.DIRCLIENT = '/dist/';
    this.DIRCLIENT_MODULES = this.DIRCLIENT + 'modules/';

    this.MIN_ONE_PANEL_WIDTH    = 1155;
    this.HOST                   = location.origin ||
                                  location.protocol + '//' + location.host;

    const TITLE = 'Cloud Commander';
    this.TITLE = TITLE;

    this.sort = {
        left: 'name',
        right: 'name',
    };

    this.order = {
        left: 'asc',
        right: 'asc',
    };

    log.enable = () => {
        Debug = true;
    };

    log.disable = () => {
        Debug = false;
    };

    const kebabToCamelCase = Util.kebabToCamelCase;

    /**
     * Функция привязываеться ко всем ссылкам и
     *  загружает содержимое каталогов
     *
     * @param params - {
     *          paramLink - ссылка
     *          needRefresh - необходимость обязательной загрузки данных с сервера
     *          panel
     *      }
     * @param callback
     */
    this.loadDir = (params, callback) => {
        const p = params;

        const refresh = p.isRefresh;
        const panel = p.panel;
        const history = p.history;
        const noCurrent = p.noCurrent;
        const currentName = p.currentName;

        let panelChanged;
        if (!noCurrent)
            if (panel && panel !== Info.panel) {
                DOM.changePanel();
                panelChanged = true;
            }

        let imgPosition;
        if (panelChanged || refresh || !history)
            imgPosition = 'top';

        Images.show.load(imgPosition, panel);

        /* загружаем содержимое каталога */
        ajaxLoad(p.path, {
            refresh,
            history,
            noCurrent,
            currentName,
        }, panel, callback);
    };

    /**
     * function load modules
     * @params = {name, path, func, dobefore, arg}
     */
    function loadModule(params) {
        if (!params)
            return;

        let path = params.path;
        const name = params.name || path && kebabToCamelCase(path);
        const func = params.func;
        const funcName = params.funcName;
        const doBefore = params.dobefore;

        const isContain = /\.js/.test(path);

        if (!isContain)
            path += '.js';

        if (CloudCmd[name])
            return;

        CloudCmd[name] = (...args) => {
            const prefix = CloudCmd.PREFIX;
            const pathFull = prefix + CloudCmd.DIRCLIENT_MODULES + path;

            exec(doBefore);

            const done = (error) => {
                const Proto = CloudCmd[name];

                if (error || !itype.function(Proto))
                    return;

                CloudCmd[name] = applyConstructor(Proto, args);
            };

            return DOM.load.js(pathFull, func || done);
        };

        CloudCmd[name][funcName] = CloudCmd[name];
    }

    /*
     * apply arguemnts to constructor
     *
     * @param constructor
     * @param args
     */
    function applyConstructor(constructor, args) {
        const F = function () {
            return constructor.apply(this, args);
        };

        F.prototype = constructor.prototype;
        return new F();
    }

    /**
     * Конструктор CloudClient, который
     * выполняет весь функционал по
     * инициализации
     */
    this.init = (prefix, config) => {
        const func = bind(exec.parallel, [
            initModules,
            baseInit,
            loadPlugins,
            loadStyle,
            exec.with(CloudCmd.route, location.hash),
        ], noop);

        const funcBefore  = (callback) => {
            const src = prefix + '/join:' + [
                CloudCmd.DIRCLIENT_MODULES + 'polyfill.js',
                '/modules/domtokenlist-shim/dist/domtokenlist.min.js',
            ].join(':');

            DOM.loadJquery(() => {
                DOM.load.js(src, callback);
            });
        };

        CloudCmd.PREFIX = prefix;
        CloudCmd.PREFIX_URL = prefix + apiURL;

        CloudCmd.config = (key) => config[key];
        CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
        CloudCmd._config = (key, value) => {
            /*
             * should be called from config.js only
             * after successful update on server
             */

            if (key === 'password')
                return;

            config[key] = value;
        };

        if (config.oneFilePanel)
            CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;

        exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore);
    };

    function loadStyle(callback) {
        const prefix = CloudCmd.PREFIX;
        const name = prefix + '/dist/cloudcmd.common.css';

        DOM.load.css(name, callback);
    }

    function loadPlugins(callback) {
        const prefix = CloudCmd.PREFIX;
        const plugins = prefix + '/plugins.js';

        DOM.load.js(plugins, callback);
    }

    this.join = (urls) => {
        const prefix  = CloudCmd.PREFIX;

        if (!Array.isArray(urls))
            throw Error('urls should be array!');

        const noPrefixUrls = urls.map((url) => {
            return url.replace(prefix, '');
        });

        return prefix + join(noPrefixUrls);
    };

    this.route = (path) => {
        const query = path.split('/');

        if (!path)
            return;

        const [kebabModule] = query;
        const module = kebabToCamelCase(kebabModule.slice(1));

        const file = query[1];
        const current = DOM.getCurrentByName(file);

        if (file && !current) {
            const msg = formatMsg('set current file', file, 'error');
            CloudCmd.log(msg);
            return;
        }

        DOM.setCurrentFile(current);

        CloudCmd.execFromModule(module, 'show');
    };

    this.logOut = () => {
        const url = CloudCmd.PREFIX + '/logout';
        const error = () => document.location.reload();

        DOM.load.ajax({url, error});
    };

    function initModules(callback) {
        exec.if(CloudCmd.Key, () => {
            Key = new CloudCmd.Key();
            CloudCmd.Key = Key;
            Key.bind();
        }, (func) => {
            /* привязываем клавиши к функциям */
            const path = 'key.js';

            loadModule({
                path,
                func
            });
        });

        Files.get('modules', (error, modules) => {
            const showLoad = Images.show.load;

            const doBefore = {
                'edit': showLoad,
                'menu': showLoad,
            };

            const load = (name, path, dobefore) => {
                const isTmpl = path === 'template';
                const funcName = isTmpl ? 'get' : 'show';

                loadModule({
                    name,
                    path,
                    dobefore,
                    funcName,
                });
            };

            if (!modules)
                modules = [];

            modules.local.forEach((module) => {
                load(null, module, doBefore[module]);
            });

            callback();
        });
    }

    function baseInit(callback) {
        const files = DOM.getFiles();

        CloudCmd.on('current-file', DOM.updateCurrentInfo);

        /* выделяем строку с первым файлом */
        if (files)
            DOM.setCurrentFile(files[0], {
                // when hash is present
                // it should be handled with this.route
                // overwre otherwise
                history: !location.hash
            });

        const dirPath = DOM.getCurrentDirPath();
        Listeners = CloudCmd.Listeners;
        Listeners.init();

        const panels = getPanels();
        panels.forEach(Listeners.setOnPanel);

        Listeners.initKeysPanel();

        if (!CloudCmd.config('dirStorage'))
            return callback();

        Storage.get(dirPath, (error, data) => {
            if (!data) {
                data = getJSONfromFileTable();
                Storage.set(dirPath, data);
            }
            callback();
        });
    }

    function getPanels() {
        const panels = ['left'];

        if (CloudCmd.config('oneFilePanel'))
            return panels;

        return [
            ...panels,
            'right',
        ];
    }

    this.execFromModule = (moduleName, funcName, ...args) => {
        const obj = CloudCmd[moduleName];
        const isObj = itype.object(obj);

        exec.if(isObj, () => {
            const obj = CloudCmd[moduleName];
            const func = obj[funcName];

            func(...args);
        }, obj);
    };

    this.refresh = (options = {}, callback) => {
        if (!callback && typeof options === 'function') {
            callback = options;
            options = {};
        }

        const panel = options.panel || Info.panel;
        const path = DOM.getCurrentDirPath(panel);

        const isRefresh = true;
        const history = false;
        const noCurrent = options ? options.noCurrent : false;
        const currentName = options.currentName;

        CloudCmd.loadDir({
            path,
            isRefresh,
            history,
            panel,
            noCurrent,
            currentName,
        }, callback);
    };

    /**
     * Функция загружает json-данные о Файловой Системе
     * через ajax-запрос.
     * @param path - каталог для чтения
     * @param options
     * { refresh, history } - необходимость обновить данные о каталоге
     * @param panel
     * @param callback
     *
     */
    function ajaxLoad(path, options, panel, callback) {
        const create = (error, json) => {
            const RESTful = DOM.RESTful;
            const name = options.currentName || Info.name;
            const obj = jonny.parse(json);
            const isRefresh = options.refresh;
            const noCurrent = options.noCurrent;

            if (!isRefresh && json)
                return createFileTable(obj, panel, options, callback);

            const position = DOM.getPanelPosition(panel);
            const sort = CloudCmd.sort[position];
            const order = CloudCmd.order[position];

            const query = rendy('?sort={{ sort }}&order={{ order }}', {
                sort,
                order,
            });

            RESTful.read(path + query, 'json', (error, obj) => {
                if (error)
                    return;

                options.sort = sort;
                options.order = order;

                createFileTable(obj, panel, options, () => {
                    if (isRefresh && !noCurrent)
                        DOM.setCurrentByName(name);

                    exec(callback);
                });

                if (!CloudCmd.config('dirStorage'))
                    return;

                Storage.set(path, obj);
            });
        };

        if (!options)
            options    = {};

        CloudCmd.log('reading dir: "' + path + '";');

        if (!CloudCmd.config('dirStorage'))
            return create();

        Storage.get(path, create);
    }

    /**
     * Функция строит файловую таблицу
     * @param json  - данные о файлах
     * @param panelParam
     * @param history
     * @param callback
     */
    function createFileTable(json, panelParam, options, callback) {
        const {
            history,
            noCurrent,
        } = options;

        const names = ['file', 'path', 'link', 'pathLink'];

        Files.get(names, (error, templFile, templPath, templLink, templPathLink) => {
            const Dialog = DOM.Dialog;
            const panel = panelParam || DOM.getPanel();

            const {
                dir,
                name,
            } = Info;

            if (error)
                return Dialog.alert(TITLE, error.responseText);

            const childNodes = panel.childNodes;
            let i = childNodes.length;

            while (i--)
                panel.removeChild(panel.lastChild);

            panel.innerHTML = buildFromJSON({
                sort        : options.sort,
                order       : options.order,
                data        : json,
                id          : panel.id,
                prefix      : CloudCmd.PREFIX,
                template    : {
                    file        : templFile,
                    path        : templPath,
                    pathLink    : templPathLink,
                    link        : templLink
                }
            });

            Listeners.setOnPanel(panel);

            if (!noCurrent) {
                let current;

                if (name === '..' && dir !== '/')
                    current = DOM.getCurrentByName(dir);

                if (!current)
                    current = DOM.getFiles(panel)[0];

                DOM.setCurrentFile(current, {
                    history: history
                });

                CloudCmd.emit('active-dir', Info.dirPath);
            }

            exec(callback);
        });
    }

    /**
     * Функция генерирует JSON из html-таблицы файлов и
     * используеться при первом заходе в корень
     */
    function getJSONfromFileTable() {
        const path = DOM.getCurrentDirPath();
        const infoFiles = Info.files || [];

        const notParent = (current) => {
            const name = DOM.getCurrentName(current);
            return name !== '..';
        };

        const parse = (current) => {
            const name = DOM.getCurrentName(current);
            const size = DOM.getCurrentSize(current);
            const owner = DOM.getCurrentOwner(current);
            const mode = DOM.getCurrentMode(current);
            const date = DOM.getCurrentDate(current);

            return {
                name,
                size,
                mode,
                owner,
                date,
            };
        };

        const files = infoFiles
            .filter(notParent)
            .map(parse);

        const fileTable = {
            path,
            files,
        };

        return fileTable;
    }

    this.goToParentDir = () => {
        const dir = Info.dir;
        const {
            dirPath,
            parentDirPath,
        } = Info;

        if (dirPath === parentDirPath)
            return;

        const path = parentDirPath;

        CloudCmd.loadDir({path}, () => {
            const {panel} = Info;
            const current = DOM.getCurrentByName(dir);
            const first = DOM.getFiles(panel)[0];

            DOM.setCurrentFile(current || first, {
                history
            });
        });
    };
}

<|endoftext|>


<commit_before><commit_msg>Comments removed<commit_after><|endoftext|>


<commit_before>var rateLimit = require('./ratelimit');
var deepEquals = require('deep-is');
var arraydiff = require('arraydiff');
var projections = require('./projections');

module.exports = function(Livedb) {

  Livedb.prototype._sdcDbPrefix = function(options) {
    return 'livedb.db.' + ((options && options.backend) ? options.backend : 'default');
  };

  function projectResults(results, cName, projection) {
    if (!projection) return;
    var r = results.results ? results.results : results;
    for (var i = 0; i < r.length; i++) {
      var r = r[i];
      r.data = projections.projectSnapshot(r.type, projection.fields, r.data);
    }
  }

  Livedb.prototype._dbQuery = function(db, index, query, options, callback) {
    var projection = this.projections[index];

    if (projection) {
      if (db.queryProjected)
        db.queryProjected(this, projection.target, projection.fields, query, options, callback);
      else {
        db.query(this, projection.target, query, options, function(err, results) {
          if (results) projectResults(results, index, projection);
          callback(err, results);
        });
      }
    } else {
      db.query(this, index, query, options, callback);
    }
  };

  Livedb.prototype._dbQueryDoc = function(db, index, docName, query, callback) {
    var projection = this.projections[index];
    if (projection) {
      if (db.queryDocProjected) // What a mouthful!
        db.queryDocProjected(this, index, projection.target, docName, projection.fields, query, callback);
      else {
        db.queryDoc(this, index, projection.target, docName, query, function(err, result) {
          if (result) {
            result.data = projections.projectSnapshot(result.type, projection.fields, result.data);
          }
          callback(err, result);
        });
      }
    } else {
      db.queryDoc(this, index, index, docName, query, callback);
    }
  };

  Livedb.prototype.queryFetch = function(index, query, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = null;
    }

    var start = Date.now();
    var livedb = this;

    var db;
    if (options && options.backend) {
      if (!this.extraDbs.hasOwnProperty(options.backend)) return callback('Backend not found');
      db = this.extraDbs[options.backend];
    } else {
      db = this.snapshotDb;
    }
    if (db.query.length < 5)
      throw Error("Livedb query backend " + (options.backend || 'default') + " is out of date " +
        "with spec (its probably missing the 'options' parameter). Update your livedb backend library");

    if (!this.snapshotDb.query) {
      return callback('Backend does not support queries');
    }

    this._dbQuery(db, index, query, {mode:'fetch'}, function(err, resultSet) {
      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.fetch', Date.now() - start);
      }

      if (err) {
        callback(err);
      } else if (Array.isArray(resultSet)) {
        callback(null, resultSet);
      } else {
        callback(null, resultSet.results, resultSet.extra);
      }
    });
  };

  Livedb.prototype.query = function(index, query, options, callback) {
    this.queryPoll(index, query, options, callback);
  };

  // For mongo, the index is just the collection itself. For something like
  // SOLR, the index refers to the core we're actually querying.
  //
  // Options can contain:
  // backend: the backend to use, or the name of the backend (if the backend
  //  is specified in the otherDbs when the livedb instance is created)
  // poll: true, false or undefined. Set true/false to force enable/disable
  //  polling mode. Undefined will let the database decide.
  // shouldPoll: function(collection, docName, opData, index, query) {return true or false; }
  //  this is a syncronous function which can be used as an early filter for
  //  operations going through the system to reduce the load on the backend.
  // pollDelay: Minimum delay between subsequent database polls. This is used
  //  to batch updates to reduce load on the database at the expense of
  //  liveness.
  Livedb.prototype.queryPoll = function(index, query, options, callback) {
    if (!this.driver.subscribeChannels) return callback("Driver does not support polling queries");

    if (typeof options === 'function') {
      callback = options;
      options = {};
    } else if (!options) {
      options = {};
    }

    var start = Date.now();
    var livedb = this;

    var db;
    if (options.backend) {
      if (!this.extraDbs.hasOwnProperty(options.backend)) return callback('Backend not found');
      db = this.extraDbs[options.backend];
    } else {
      db = this.snapshotDb;
    }

    if (db.disableSubscribe) {
      return callback('Backend does not support subscribe');
    }

    if (!this.snapshotDb.query) {
      return callback('Backend does not support queries');
    }

    var projection = this.projections[index];
    var collection = (projection) ? projection.target : index;
    var channels = [collection];
    // subscribe to collection firehose -> cache. The firehose isn't updated until after the db,
    // so if we get notified about an op here, the document's been saved.
    this.driver.subscribeChannels(channels, function(err, stream) {
      if (err) return callback(err);
      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.driverSubscribe', Date.now() - start);
      }

      // Issue query on db to get our initial result set.
      livedb._dbQuery(db, index, query, {mode: 'initial'}, function(err, resultSet) {
        if (err) {
          stream.destroy();
          return callback(err);
        }
        if (livedb.sdc) {
          var sdcPrefix = livedb._sdcDbPrefix(options);
          livedb.sdc.timing(sdcPrefix + '.query.subscribe', Date.now() - start);
        }
        var results, extra;
        if (Array.isArray(resultSet)) {
          results = resultSet;
        } else {
          results = resultSet.results;
          extra = resultSet.extra;
        }
        var docNames = mapResults(results);
        var queryEmitter = new QueryEmitter(index, query, stream, docNames, extra);
        var pollQuery = getPollQuery(livedb, db, options, queryEmitter)
        startStream(livedb, db, options, queryEmitter, collection, pollQuery);
        callback(null, queryEmitter, results, extra);
      });
    });
  };
};

function startStream(livedb, db, options, queryEmitter, collection, pollQuery) {
  function readStream() {
    var data;
    while (data = queryEmitter.stream.read()) {
      readStreamItem(livedb, db, options, queryEmitter, collection, pollQuery, data);
    }
  }
  readStream();
  queryEmitter.stream.on('readable', readStream);
  return queryEmitter;
}

function getPollQuery(livedb, db, options, queryEmitter) {
  var poll = (!db.queryDoc) ? true :
    (options.poll != null) ? options.poll :
    (db.queryNeedsPollMode) ? db.queryNeedsPollMode(queryEmitter.index, queryEmitter.query) :
    true;
  if (!poll) return;

  // Default to 2 seconds
  var delay = (typeof options.pollDelay === 'number') ? options.pollDelay : 2000;
  return rateLimit(delay, function pollQuery() {
    // We need to do a full poll of the query, because the query uses limits or something.
    var start = Date.now();
    livedb._dbQuery(db, queryEmitter.index, queryEmitter.query, {mode: 'poll'}, function(err, resultSet) {
      if (err) return queryEmitter.emitError(err);

      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.poll', Date.now() - start);
      }

      var results, extra;
      if (Array.isArray(resultSet)) {
        results = resultSet;
      } else {
        results = resultSet.results;
        extra = resultSet.extra;
      }
      queryEmitter.emitResults(results, extra);
    });
  });
}

function readStreamItem(livedb, db, options, queryEmitter, collection, pollQuery, data) {
  // Ignore if the user or database say we don't need to poll
  if (options.shouldPoll && !options.shouldPoll(collection, data.docName, data, queryEmitter.index, queryEmitter.query)) return;
  if (db.queryShouldPoll && !db.queryShouldPoll(this, collection, data.docName, data, queryEmitter.index, queryEmitter.query)) return;

  // Not sure whether the changed document should be in the result set
  if (pollQuery) return pollQuery();

  var start = Date.now();
  livedb._dbQueryDoc(db, queryEmitter.index, data.docName, queryEmitter.query, function(err, result) {
    if (err === 'Irrelevant') return;
    if (err) return queryEmitter.emitError(err);

    if (livedb.sdc) {
      var sdcPrefix = livedb._sdcDbPrefix(options);
      livedb.sdc.timing(sdcPrefix + '.query.pollDoc', Date.now() - start);
    }

    // Check if the document was in the previous results set
    var i = queryEmitter.docNames.indexOf(data.docName);

    if (i === -1 && result) {
      // Add doc to the collection. Order isn't important, so
      // we'll just whack it at the end.
      var index = queryEmitter.docNames.push(result.docName) - 1;
      var values = [result];
      queryEmitter.emitDiff([new arraydiff.InsertDiff(index, values)]);

    } else if (i !== -1 && !result) {
      queryEmitter.docNames.splice(i, 1);
      queryEmitter.emitDiff([new arraydiff.RemoveDiff(i, 1)]);
    }
  });
}

function QueryEmitter(index, query, stream, docNames, extra) {
  this.index = index;
  this.query = query;
  this.stream = stream;
  this.docNames = docNames;
  this.extra = extra;
}

QueryEmitter.prototype.destroy = function() {
  this.stream.destroy();
  this.onError = doNothing;
  this.onDiff = doNothing;
  this.onExtra = doNothing;
  this.emitError = doNothing;
  this.emitDiff = doNothing;
  this.emitExtra = doNothing;
};

QueryEmitter.prototype.emitResults = function(results, extra) {
  var docNames = mapResults(results);
  var docNamesDiff = arraydiff(this.docNames, docNames);
  if (docNamesDiff.length) {
    this.docNames = docNames;
    var diff = mapDiff(docNamesDiff, results);
    this.emitDiff(diff);
  }
  // Be nice to not have to do this in such a brute force way
  if (!deepEquals(this.extra, extra)) {
    this.extra = extra;
    this.emitExtra(extra);
  }
};

// These methods can be overriden to extend default behavior
QueryEmitter.prototype.emitError = function(err) {
  this.onError(err);
};
QueryEmitter.prototype.emitDiff = function(diff) {
  this.onDiff(diff);
};
QueryEmitter.prototype.emitExtra = function(extra) {
  this.onExtra(extra);
};

// Clients should define these functions
QueryEmitter.prototype.onError = doNothing;
QueryEmitter.prototype.onDiff = doNothing;
QueryEmitter.prototype.onExtra = doNothing;

function doNothing() {}

function mapResults(results) {
  var docNames = [];
  for (var i = 0; i < results.length; i++) {
    docNames.push(results[i].docName);
  }
  return docNames;
}

function mapDiff(docNamesDiff, results) {
  var diff = [];
  var resultsMap;
  for (var i = 0; i < docNamesDiff.length; i++) {
    var item = docNamesDiff[i];
    if (item instanceof arraydiff.InsertDiff) {
      if (!resultsMap) resultsMap = getResultsMap(results);
      var values = [];
      for (var j = 0; j < item.values.length; j++) {
        var docName = item.values[j];
        values.push(resultsMap[docName]);
      }
      diff.push(new arraydiff.InsertDiff(item.index, values));
    } else {
      diff.push(item);
    }
  }
  return diff;
}

function getResultsMap(results) {
  var resultsMap = {};
  for (var i = 0; i < results.length; i++) {
    var result = results[i];
    resultsMap[result.docName] = result;
  }
  return resultsMap;
}
<commit_msg>emit ops from queries so that docs don't have to be subscribed separately<commit_after>var rateLimit = require('./ratelimit');
var deepEquals = require('deep-is');
var arraydiff = require('arraydiff');
var projections = require('./projections');

module.exports = function(Livedb) {

  Livedb.prototype._sdcDbPrefix = function(options) {
    return 'livedb.db.' + ((options && options.backend) ? options.backend : 'default');
  };

  function projectResults(results, cName, projection) {
    if (!projection) return;
    var r = results.results ? results.results : results;
    for (var i = 0; i < r.length; i++) {
      var r = r[i];
      r.data = projections.projectSnapshot(r.type, projection.fields, r.data);
    }
  }

  Livedb.prototype._dbQuery = function(db, index, query, options, callback) {
    var projection = this.projections[index];

    if (projection) {
      if (db.queryProjected)
        db.queryProjected(this, projection.target, projection.fields, query, options, callback);
      else {
        db.query(this, projection.target, query, options, function(err, results) {
          if (results) projectResults(results, index, projection);
          callback(err, results);
        });
      }
    } else {
      db.query(this, index, query, options, callback);
    }
  };

  Livedb.prototype._dbQueryDoc = function(db, index, docName, query, callback) {
    var projection = this.projections[index];
    if (projection) {
      if (db.queryDocProjected) // What a mouthful!
        db.queryDocProjected(this, index, projection.target, docName, projection.fields, query, callback);
      else {
        db.queryDoc(this, index, projection.target, docName, query, function(err, result) {
          if (result) {
            result.data = projections.projectSnapshot(result.type, projection.fields, result.data);
          }
          callback(err, result);
        });
      }
    } else {
      db.queryDoc(this, index, index, docName, query, callback);
    }
  };

  Livedb.prototype.queryFetch = function(index, query, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = null;
    }

    var start = Date.now();
    var livedb = this;

    var db;
    if (options && options.backend) {
      if (!this.extraDbs.hasOwnProperty(options.backend)) return callback('Backend not found');
      db = this.extraDbs[options.backend];
    } else {
      db = this.snapshotDb;
    }
    if (db.query.length < 5)
      throw Error("Livedb query backend " + (options.backend || 'default') + " is out of date " +
        "with spec (its probably missing the 'options' parameter). Update your livedb backend library");

    if (!this.snapshotDb.query) {
      return callback('Backend does not support queries');
    }

    this._dbQuery(db, index, query, {mode:'fetch'}, function(err, resultSet) {
      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.fetch', Date.now() - start);
      }

      if (err) {
        callback(err);
      } else if (Array.isArray(resultSet)) {
        callback(null, resultSet);
      } else {
        callback(null, resultSet.results, resultSet.extra);
      }
    });
  };

  Livedb.prototype.query = function(index, query, options, callback) {
    this.queryPoll(index, query, options, callback);
  };

  // For mongo, the index is just the collection itself. For something like
  // SOLR, the index refers to the core we're actually querying.
  //
  // Options can contain:
  // backend: the backend to use, or the name of the backend (if the backend
  //  is specified in the otherDbs when the livedb instance is created)
  // poll: true, false or undefined. Set true/false to force enable/disable
  //  polling mode. Undefined will let the database decide.
  // shouldPoll: function(collection, docName, opData, index, query) {return true or false; }
  //  this is a syncronous function which can be used as an early filter for
  //  operations going through the system to reduce the load on the backend.
  // pollDelay: Minimum delay between subsequent database polls. This is used
  //  to batch updates to reduce load on the database at the expense of
  //  liveness.
  Livedb.prototype.queryPoll = function(index, query, options, callback) {
    if (!this.driver.subscribeCollection) return callback("Driver does not support polling queries");

    if (typeof options === 'function') {
      callback = options;
      options = {};
    } else if (!options) {
      options = {};
    }

    var start = Date.now();
    var livedb = this;

    var db;
    if (options.backend) {
      if (!this.extraDbs.hasOwnProperty(options.backend)) return callback('Backend not found');
      db = this.extraDbs[options.backend];
    } else {
      db = this.snapshotDb;
    }

    if (db.disableSubscribe) {
      return callback('Backend does not support subscribe');
    }

    if (!this.snapshotDb.query) {
      return callback('Backend does not support queries');
    }

    var projection = this.projections[index];
    var collection = (projection) ? projection.target : index;
    // subscribe to collection firehose -> cache. The firehose isn't updated until after the db,
    // so if we get notified about an op here, the document's been saved.
    this.driver.subscribeCollection(collection, function(err, stream) {
      if (err) return callback(err);
      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.driverSubscribe', Date.now() - start);
      }

      // Issue query on db to get our initial result set.
      livedb._dbQuery(db, index, query, {mode: 'initial'}, function(err, resultSet) {
        if (err) {
          stream.destroy();
          return callback(err);
        }
        if (livedb.sdc) {
          var sdcPrefix = livedb._sdcDbPrefix(options);
          livedb.sdc.timing(sdcPrefix + '.query.subscribe', Date.now() - start);
        }
        var results, extra;
        if (Array.isArray(resultSet)) {
          results = resultSet;
        } else {
          results = resultSet.results;
          extra = resultSet.extra;
        }
        var docNames = mapResults(results);
        var queryEmitter = new QueryEmitter(index, query, stream, docNames, extra, projection);
        var pollQuery = getPollQuery(livedb, db, options, queryEmitter)
        startStream(livedb, db, options, queryEmitter, collection, pollQuery);
        callback(null, queryEmitter, results, extra);
      });
    });
  };
};

function startStream(livedb, db, options, queryEmitter, collection, pollQuery) {
  function readStream() {
    var data;
    while (data = queryEmitter.stream.read()) {
      queryEmitter.emitOp(data);
      readStreamItem(livedb, db, options, queryEmitter, collection, pollQuery, data);
    }
  }
  readStream();
  queryEmitter.stream.on('readable', readStream);
  return queryEmitter;
}

function getPollQuery(livedb, db, options, queryEmitter) {
  var poll = (!db.queryDoc) ? true :
    (options.poll != null) ? options.poll :
    (db.queryNeedsPollMode) ? db.queryNeedsPollMode(queryEmitter.index, queryEmitter.query) :
    true;
  if (!poll) return;

  // Default to 2 seconds
  var delay = (typeof options.pollDelay === 'number') ? options.pollDelay : 2000;
  return rateLimit(delay, function pollQuery() {
    // We need to do a full poll of the query, because the query uses limits or something.
    var start = Date.now();
    livedb._dbQuery(db, queryEmitter.index, queryEmitter.query, {mode: 'poll'}, function(err, resultSet) {
      if (err) return queryEmitter.emitError(err);

      if (livedb.sdc) {
        var sdcPrefix = livedb._sdcDbPrefix(options);
        livedb.sdc.timing(sdcPrefix + '.query.poll', Date.now() - start);
      }

      var results, extra;
      if (Array.isArray(resultSet)) {
        results = resultSet;
      } else {
        results = resultSet.results;
        extra = resultSet.extra;
      }
      queryEmitter.emitResults(results, extra);
    });
  });
}

function readStreamItem(livedb, db, options, queryEmitter, collection, pollQuery, data) {
  // Ignore if the user or database say we don't need to poll
  if (options.shouldPoll && !options.shouldPoll(collection, data.docName, data, queryEmitter.index, queryEmitter.query)) return;
  if (db.queryShouldPoll && !db.queryShouldPoll(this, collection, data.docName, data, queryEmitter.index, queryEmitter.query)) return;

  // Not sure whether the changed document should be in the result set
  if (pollQuery) return pollQuery();

  var start = Date.now();
  livedb._dbQueryDoc(db, queryEmitter.index, data.docName, queryEmitter.query, function(err, result) {
    if (err === 'Irrelevant') return;
    if (err) return queryEmitter.emitError(err);

    if (livedb.sdc) {
      var sdcPrefix = livedb._sdcDbPrefix(options);
      livedb.sdc.timing(sdcPrefix + '.query.pollDoc', Date.now() - start);
    }

    // Check if the document was in the previous results set
    var i = queryEmitter.docNames.indexOf(data.docName);

    if (i === -1 && result) {
      // Add doc to the collection. Order isn't important, so
      // we'll just whack it at the end.
      var index = queryEmitter.docNames.push(result.docName) - 1;
      var values = [result];
      queryEmitter.emitDiff([new arraydiff.InsertDiff(index, values)]);

    } else if (i !== -1 && !result) {
      queryEmitter.docNames.splice(i, 1);
      queryEmitter.emitDiff([new arraydiff.RemoveDiff(i, 1)]);
    }
  });
}

function QueryEmitter(index, query, stream, docNames, extra, projection) {
  this.index = index;
  this.query = query;
  this.stream = stream;
  this.docNames = docNames;
  this.extra = extra;
  this.projection = projection;
}

QueryEmitter.prototype.destroy = function() {
  this.stream.destroy();
};

QueryEmitter.prototype.emitResults = function(results, extra) {
  var docNames = mapResults(results);
  var docNamesDiff = arraydiff(this.docNames, docNames);
  if (docNamesDiff.length) {
    this.docNames = docNames;
    var diff = mapDiff(docNamesDiff, results);
    this.emitDiff(diff);
  }
  // Be nice to not have to do this in such a brute force way
  if (!deepEquals(this.extra, extra)) {
    this.extra = extra;
    this.emitExtra(extra);
  }
};

// These methods can be overriden to extend default behavior
QueryEmitter.prototype.emitError = function(err) {
  this.onError(err);
};
QueryEmitter.prototype.emitDiff = function(diff) {
  this.onDiff(diff);
};
QueryEmitter.prototype.emitExtra = function(extra) {
  this.onExtra(extra);
};
QueryEmitter.prototype.emitOp = function(data) {
  if (this.docNames.indexOf(data.docName) === -1) return;
  if (this.projection) {
    var projected = projections.projectOpData(this.projection.type, this.projection.fields, data);
    projected.docName = data.docName;
    projected.collection = this.index;
    this.onOp(projected);
  } else {
    this.onOp(data);
  }
};

// Clients should define these functions
QueryEmitter.prototype.onError = doNothing;
QueryEmitter.prototype.onDiff = doNothing;
QueryEmitter.prototype.onExtra = doNothing;
QueryEmitter.prototype.onOp = doNothing;

function doNothing() {}

function mapResults(results) {
  var docNames = [];
  for (var i = 0; i < results.length; i++) {
    docNames.push(results[i].docName);
  }
  return docNames;
}

function mapDiff(docNamesDiff, results) {
  var diff = [];
  var resultsMap;
  for (var i = 0; i < docNamesDiff.length; i++) {
    var item = docNamesDiff[i];
    if (item instanceof arraydiff.InsertDiff) {
      if (!resultsMap) resultsMap = getResultsMap(results);
      var values = [];
      for (var j = 0; j < item.values.length; j++) {
        var docName = item.values[j];
        values.push(resultsMap[docName]);
      }
      diff.push(new arraydiff.InsertDiff(item.index, values));
    } else {
      diff.push(item);
    }
  }
  return diff;
}

function getResultsMap(results) {
  var resultsMap = {};
  for (var i = 0; i < results.length; i++) {
    var result = results[i];
    resultsMap[result.docName] = result;
  }
  return resultsMap;
}
<|endoftext|>


<commit_before>
(function (bs) {
  'use strict';
  var TEMPLATE_CHANGED_EVENT = 'injularTemplate:changed';
  var SCRIPT_CHANGED_EVENT = 'injularScript:changed';
  var DIRECTIVE_SUFFIX = 'Directive';
  var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;

  var sockets = bs.socket;

  sockets.on(TEMPLATE_CHANGED_EVENT, templateChangedListener);
  sockets.on(SCRIPT_CHANGED_EVENT, scriptChangedListener);

  function templateChangedListener(data) {
    var $injector = getInjector();

    var $templateCache = $injector.get('$templateCache');
    var templateUrl = data.templateUrl;
    var template = data.template;
    replaceTemplateCache($templateCache, templateUrl, template);

    if (data.reloadRoute) {
      reloadRoute($injector);
    } else {
      var $compile = $injector.get('$compile');
      replaceTemplateInDom($compile, templateUrl, template);
    }
  }

  function scriptChangedListener(data) {
    var $injector = getInjector();

    var localAngular = getLocalAngular($injector);
    localAngular._scriptUrl = data.scriptUrl;
    var jsInjector = new Function('angular', data.script);
    jsInjector(localAngular);
    removeDeletedDirectives($injector, data.scriptUrl);

    reloadRoute($injector);
  }

  function getBsInjular() {
    var bsInjular = window.___bsInjular___;

    if (!bsInjular) {
      throwError(
        'Could not get window.___bsInjular___ . ' +
        'Are you sure the moduleName in the bsInjular options is correct?'
      );
    }

    return bsInjular;
  }

  function removeDeletedDirectives($injector, scriptUrl) {
    if (!scriptUrl) {
      return;
    }
    var bsInjular = getBsInjular();
    var indexByDirectiveName = bsInjular.indexByDirectiveName;
    var directivesByUrl = bsInjular.directivesByUrl;
    if (!directivesByUrl) {
      return;
    }
    var directivesByName = directivesByUrl[scriptUrl];

    if (directivesByName) {
      for (var name in directivesByName) {
        if (directivesByName.hasOwnProperty(name)) {
          var removeStartIndex = indexByDirectiveName[name] || 0;
          var directiveList = directivesByName[name].slice(removeStartIndex);
          if (directiveList.length) {
            var moduleDirectives = $injector.get(name + DIRECTIVE_SUFFIX);
            angular.forEach(directiveList, function(directive) {
              var index = moduleDirectives.indexOf(directive);
              if (index >= 0) {
                moduleDirectives.splice(index, 1);
              }
            });
          }
        }
      }
    } else {
      console.warn('[BS-Injular] No directives for url: ' + scriptUrl);
    }
  }

  function getLocalAngular($injector) {
    var bsInjular = getBsInjular();

    if (!bsInjular.localAngular) {
      bsInjular.localAngular = createLocalAngular(bsInjular, $injector);
    }
    bsInjular.indexByDirectiveName = {};

    return bsInjular.localAngular;
  }

  function reloadRoute($injector) {
    if ($injector.has('$state')) {
      $injector.get('$state').reload();
    } else if ($injector.has('$route')) {
      $injector.get('$route').reload();
    } else {
      throwError(
        'Cannot reload route because ' +
        'neither $state nor $route are present in $injector'
      );
    }
  }

  function getAngular() {
    var angular = window.angular;

    if (!angular) {
      throwError('Can\'t find window.angular');
    }

    return angular;
  }

  function getAppElement() {
    if (!document.querySelector) {
      throwError('Can\'t find document.querySelector');
    }

    var elem = document.querySelector('[ng-app]');

    if (!elem) {
      throwError('Can\'t find [ng-app] element');
    }

    return elem;
  }

  function getInjector() {
    var angular = getAngular();
    var elem = getAppElement();
    var $injector = angular.element(elem).injector();

    if (!$injector) {
      throwError('Can\'t find $injector in [ng-app] element');
    }

    return $injector;
  }

  function replaceTemplateCache($templateCache, templateUrl, template) {
    var cacheUrls = [];
    var cacheUrl = templateUrl;
    cacheUrls.push(cacheUrl);
    var cache = $templateCache.get(cacheUrl);

    if (!cache && templateUrl.charAt(0) === '/') {
      cacheUrl = templateUrl.slice(1);
      cacheUrls.push(cacheUrl);
      cache = $templateCache.get(cacheUrl);
    }

    if (!cache && templateUrl.lastIndexOf(location.pathname, 0) === 0) {
      cacheUrl = templateUrl.slice(location.pathname.length);
      cacheUrls.push(cacheUrl);
      cache = $templateCache.get(cacheUrl);
    }

    if (!cache) {
      throwError('Can\'t find templateCache of any of the following urls: ' + cacheUrls);
    }

    $templateCache.remove(cacheUrl);
    $templateCache.put(cacheUrl, template);
  }

  function replaceTemplateInDom($compile, templateUrl, template) {
    var tw = createInjularCommentWalker(templateUrl);

    var node;
    while ((node = tw.nextNode())) {
      var templateNodes = getTemplateNodes(node, templateUrl);
      var templateElements = angular.element(templateNodes);
      var scope = templateElements.scope();

      scope.$apply(function() {
        var newTemplateElements = $compile(template)(scope);
        templateElements.replaceWith(newTemplateElements);
      });
    }
  }

  function getTemplateNodes(startingNode, templateUrl) {
    var nodes = [startingNode];

    var node = startingNode;
    while ((node = node.nextSibling)) {
      nodes.push(node);
      if (node.nodeType === Node.COMMENT_NODE &&
          node.textContent === 'bs-injular-end ' + templateUrl) {
        return nodes;
      }
    }

    throwError('Can\'t find ending comment node: bs-injular-end ' + templateUrl);
  }

  function createInjularCommentWalker(templateUrl) {
    var tw, node;
    var root = getAppElement();

    if (document.createTreeWalker) {
      tw = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null, null);
    } else {
      // for IE8
      tw = createTreeWalker(root);
    }

    return {
      nextNode: nextNode
    };

    function nextNode() {
      while ((node = tw.nextNode())) {
        if (node.nodeType === Node.COMMENT_NODE &&
            node.textContent === 'bs-injular-start ' + templateUrl) {
          return node;
        }
      }
      return node;
    }
  }

  function createTreeWalker(root) {
    var node = {childNodes: [root]};
    var stack = [];
    return {
      nextNode: nextNode
    };

    function nextNode() {
      if (!node) {
        return null;
      }

      var childNodes = node.childNodes;
      if (childNodes && childNodes.length) {
        if (node.nextSibling) {
          stack.push(node);
        }
        node = childNodes[0];
        return node;
      }

      node = stack.pop();
      if (!node) {
        return null;
      }

      node = node.nextSibling;
      return node;
    }
  }

  function createLocalAngular(bsInjular, $injector) {
    var angular = getAngular();
    var localAngular = angular.copy(angular);
    var moduleFn = localAngular.module;
    localAngular.module = injularModule;
    return localAngular;

    function injularModule() {
      var app = moduleFn.apply(this, arguments);
      app = angular.copy(app);
      app.controller = injularControllerRecipe;
      app.directive = injularDirectiveRecipe;
      if ('component' in app) {
        if ('$compileProvider' in bsInjular) {
          app.component = getCompileProvider().component.bind(app);
        } else {
          app.component = getCompileProvider;
        }
      }
      app.config = returnSelf;
      app.run = returnSelf;
      app.constant = returnSelf;
      app.value = returnSelf;
      app.provider = returnSelf;
      app.factory = returnSelf;
      app.service = returnSelf;
      return app;
    }

    function returnSelf() {
      return this;
    }

    function injularControllerRecipe() {
      if (!bsInjular.$controllerProvider) {
        throwError(
          'Could not get $controllerProvider. ' +
          'Are you sure the moduleName in the bsInjular options is correct?'
        );
      }
      bsInjular.$controllerProvider.register.apply(bsInjular.$controllerProvider, arguments);
      return this;
    }

    function injularDirectiveRecipe(name, directiveFactory) {
      var directivesByUrl = bsInjular.directivesByUrl;
      if (!directivesByUrl) {
        throwError(
          'Could not get directivesByUrl. ' +
          'Are you sure angularFile property in bsInjular options is correct?'
        );
      }

      var directivesByName = directivesByUrl[localAngular._scriptUrl];

      if (directivesByName) {
        if (angular.isString(name)) {
          injectDirective(name, directiveFactory, directivesByName);
        } else {
          angular.forEach(name, function(directiveFactory, name) {
            injectDirective(name, directiveFactory, directivesByName);
          });
        }
      } else {
        console.warn('[BS-Injular] No directives for url: ' + localAngular._scriptUrl);
      }

      return this;
    }

    function injectDirective(name, directiveFactory, directivesByName) {
      var directiveList = directivesByName[name];

      if (!bsInjular.indexByDirectiveName.hasOwnProperty(name)) {
        bsInjular.indexByDirectiveName[name] = 0;
      }
      var index = bsInjular.indexByDirectiveName[name]++;

      if (directiveList) {
        if (index < directiveList.length) {
          var directive = directiveList[index];
          var newDirective = instantiateDirective($injector, directiveFactory, name);
          removeReplaceableDirectiveProperties(directive);
          angular.extend(directive, newDirective);
          return this;
        }
      } else {
        directiveList = directivesByName[name] = [];
      }

      // create directive if it does not exists

      var $compileProvider = getCompileProvider();
      $compileProvider.directive(name, directiveFactory);
      var directives = $injector.get(name + DIRECTIVE_SUFFIX);
      directiveList.push(directives[directives.length - 1]);
      bsInjular.indexByDirectiveName[name]++;
    }

    function getCompileProvider() {
      var $compileProvider = bsInjular.$compileProvider;
      if (!$compileProvider) {
        throwError(
          'Could not get $compileProvider. ' +
          'Are you sure the moduleName in the bsInjular options is correct?'
        );
      }
      return $compileProvider;
    }
  }

  function instantiateDirective($injector, directiveFactory, name) {
    // Code from $compileProvider.directive
    var directive = $injector.invoke(directiveFactory);
    if (angular.isFunction(directive)) {
      directive = { compile: valueFn(directive) };
    } else if (!directive.compile && directive.link) {
      directive.compile = valueFn(directive.link);
    }
    directive.priority = directive.priority || 0;
    directive.name = directive.name || name;
    directive.require = directive.require || (directive.controller && directive.name);
    directive.restrict = directive.restrict || 'EA';
    return directive;
  }

  function removeReplaceableDirectiveProperties(directive) {
    for (var key in directive) {
      if (key !== 'index' && key !== '$$moduleName') {
        delete directive[key];
      }
    }
  }

  function valueFn(value) {
    return function() {
      return value;
    };
  }

  function throwError(msg) {
    throw new Error('[BS-Injular] ' + msg);
  }
})(window.___browserSync___);
<commit_msg>Remove unused var<commit_after>
(function (bs) {
  'use strict';
  var TEMPLATE_CHANGED_EVENT = 'injularTemplate:changed';
  var SCRIPT_CHANGED_EVENT = 'injularScript:changed';
  var DIRECTIVE_SUFFIX = 'Directive';

  var sockets = bs.socket;

  sockets.on(TEMPLATE_CHANGED_EVENT, templateChangedListener);
  sockets.on(SCRIPT_CHANGED_EVENT, scriptChangedListener);

  function templateChangedListener(data) {
    var $injector = getInjector();

    var $templateCache = $injector.get('$templateCache');
    var templateUrl = data.templateUrl;
    var template = data.template;
    replaceTemplateCache($templateCache, templateUrl, template);

    if (data.reloadRoute) {
      reloadRoute($injector);
    } else {
      var $compile = $injector.get('$compile');
      replaceTemplateInDom($compile, templateUrl, template);
    }
  }

  function scriptChangedListener(data) {
    var $injector = getInjector();

    var localAngular = getLocalAngular($injector);
    localAngular._scriptUrl = data.scriptUrl;
    var jsInjector = new Function('angular', data.script);
    jsInjector(localAngular);
    removeDeletedDirectives($injector, data.scriptUrl);

    reloadRoute($injector);
  }

  function getBsInjular() {
    var bsInjular = window.___bsInjular___;

    if (!bsInjular) {
      throwError(
        'Could not get window.___bsInjular___ . ' +
        'Are you sure the moduleName in the bsInjular options is correct?'
      );
    }

    return bsInjular;
  }

  function removeDeletedDirectives($injector, scriptUrl) {
    if (!scriptUrl) {
      return;
    }
    var bsInjular = getBsInjular();
    var indexByDirectiveName = bsInjular.indexByDirectiveName;
    var directivesByUrl = bsInjular.directivesByUrl;
    if (!directivesByUrl) {
      return;
    }
    var directivesByName = directivesByUrl[scriptUrl];

    if (directivesByName) {
      for (var name in directivesByName) {
        if (directivesByName.hasOwnProperty(name)) {
          var removeStartIndex = indexByDirectiveName[name] || 0;
          var directiveList = directivesByName[name].slice(removeStartIndex);
          if (directiveList.length) {
            var moduleDirectives = $injector.get(name + DIRECTIVE_SUFFIX);
            angular.forEach(directiveList, function(directive) {
              var index = moduleDirectives.indexOf(directive);
              if (index >= 0) {
                moduleDirectives.splice(index, 1);
              }
            });
          }
        }
      }
    } else {
      console.warn('[BS-Injular] No directives for url: ' + scriptUrl);
    }
  }

  function getLocalAngular($injector) {
    var bsInjular = getBsInjular();

    if (!bsInjular.localAngular) {
      bsInjular.localAngular = createLocalAngular(bsInjular, $injector);
    }
    bsInjular.indexByDirectiveName = {};

    return bsInjular.localAngular;
  }

  function reloadRoute($injector) {
    if ($injector.has('$state')) {
      $injector.get('$state').reload();
    } else if ($injector.has('$route')) {
      $injector.get('$route').reload();
    } else {
      throwError(
        'Cannot reload route because ' +
        'neither $state nor $route are present in $injector'
      );
    }
  }

  function getAngular() {
    var angular = window.angular;

    if (!angular) {
      throwError('Can\'t find window.angular');
    }

    return angular;
  }

  function getAppElement() {
    if (!document.querySelector) {
      throwError('Can\'t find document.querySelector');
    }

    var elem = document.querySelector('[ng-app]');

    if (!elem) {
      throwError('Can\'t find [ng-app] element');
    }

    return elem;
  }

  function getInjector() {
    var angular = getAngular();
    var elem = getAppElement();
    var $injector = angular.element(elem).injector();

    if (!$injector) {
      throwError('Can\'t find $injector in [ng-app] element');
    }

    return $injector;
  }

  function replaceTemplateCache($templateCache, templateUrl, template) {
    var cacheUrls = [];
    var cacheUrl = templateUrl;
    cacheUrls.push(cacheUrl);
    var cache = $templateCache.get(cacheUrl);

    if (!cache && templateUrl.charAt(0) === '/') {
      cacheUrl = templateUrl.slice(1);
      cacheUrls.push(cacheUrl);
      cache = $templateCache.get(cacheUrl);
    }

    if (!cache && templateUrl.lastIndexOf(location.pathname, 0) === 0) {
      cacheUrl = templateUrl.slice(location.pathname.length);
      cacheUrls.push(cacheUrl);
      cache = $templateCache.get(cacheUrl);
    }

    if (!cache) {
      throwError('Can\'t find templateCache of any of the following urls: ' + cacheUrls);
    }

    $templateCache.remove(cacheUrl);
    $templateCache.put(cacheUrl, template);
  }

  function replaceTemplateInDom($compile, templateUrl, template) {
    var tw = createInjularCommentWalker(templateUrl);

    var node;
    while ((node = tw.nextNode())) {
      var templateNodes = getTemplateNodes(node, templateUrl);
      var templateElements = angular.element(templateNodes);
      var scope = templateElements.scope();

      scope.$apply(function() {
        var newTemplateElements = $compile(template)(scope);
        templateElements.replaceWith(newTemplateElements);
      });
    }
  }

  function getTemplateNodes(startingNode, templateUrl) {
    var nodes = [startingNode];

    var node = startingNode;
    while ((node = node.nextSibling)) {
      nodes.push(node);
      if (node.nodeType === Node.COMMENT_NODE &&
          node.textContent === 'bs-injular-end ' + templateUrl) {
        return nodes;
      }
    }

    throwError('Can\'t find ending comment node: bs-injular-end ' + templateUrl);
  }

  function createInjularCommentWalker(templateUrl) {
    var tw, node;
    var root = getAppElement();

    if (document.createTreeWalker) {
      tw = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null, null);
    } else {
      // for IE8
      tw = createTreeWalker(root);
    }

    return {
      nextNode: nextNode
    };

    function nextNode() {
      while ((node = tw.nextNode())) {
        if (node.nodeType === Node.COMMENT_NODE &&
            node.textContent === 'bs-injular-start ' + templateUrl) {
          return node;
        }
      }
      return node;
    }
  }

  function createTreeWalker(root) {
    var node = {childNodes: [root]};
    var stack = [];
    return {
      nextNode: nextNode
    };

    function nextNode() {
      if (!node) {
        return null;
      }

      var childNodes = node.childNodes;
      if (childNodes && childNodes.length) {
        if (node.nextSibling) {
          stack.push(node);
        }
        node = childNodes[0];
        return node;
      }

      node = stack.pop();
      if (!node) {
        return null;
      }

      node = node.nextSibling;
      return node;
    }
  }

  function createLocalAngular(bsInjular, $injector) {
    var angular = getAngular();
    var localAngular = angular.copy(angular);
    var moduleFn = localAngular.module;
    localAngular.module = injularModule;
    return localAngular;

    function injularModule() {
      var app = moduleFn.apply(this, arguments);
      app = angular.copy(app);
      app.controller = injularControllerRecipe;
      app.directive = injularDirectiveRecipe;
      if ('component' in app) {
        if ('$compileProvider' in bsInjular) {
          app.component = getCompileProvider().component.bind(app);
        } else {
          app.component = getCompileProvider;
        }
      }
      app.config = returnSelf;
      app.run = returnSelf;
      app.constant = returnSelf;
      app.value = returnSelf;
      app.provider = returnSelf;
      app.factory = returnSelf;
      app.service = returnSelf;
      return app;
    }

    function returnSelf() {
      return this;
    }

    function injularControllerRecipe() {
      if (!bsInjular.$controllerProvider) {
        throwError(
          'Could not get $controllerProvider. ' +
          'Are you sure the moduleName in the bsInjular options is correct?'
        );
      }
      bsInjular.$controllerProvider.register.apply(bsInjular.$controllerProvider, arguments);
      return this;
    }

    function injularDirectiveRecipe(name, directiveFactory) {
      var directivesByUrl = bsInjular.directivesByUrl;
      if (!directivesByUrl) {
        throwError(
          'Could not get directivesByUrl. ' +
          'Are you sure angularFile property in bsInjular options is correct?'
        );
      }

      var directivesByName = directivesByUrl[localAngular._scriptUrl];

      if (directivesByName) {
        if (angular.isString(name)) {
          injectDirective(name, directiveFactory, directivesByName);
        } else {
          angular.forEach(name, function(directiveFactory, name) {
            injectDirective(name, directiveFactory, directivesByName);
          });
        }
      } else {
        console.warn('[BS-Injular] No directives for url: ' + localAngular._scriptUrl);
      }

      return this;
    }

    function injectDirective(name, directiveFactory, directivesByName) {
      var directiveList = directivesByName[name];

      if (!bsInjular.indexByDirectiveName.hasOwnProperty(name)) {
        bsInjular.indexByDirectiveName[name] = 0;
      }
      var index = bsInjular.indexByDirectiveName[name]++;

      if (directiveList) {
        if (index < directiveList.length) {
          var directive = directiveList[index];
          var newDirective = instantiateDirective($injector, directiveFactory, name);
          removeReplaceableDirectiveProperties(directive);
          angular.extend(directive, newDirective);
          return this;
        }
      } else {
        directiveList = directivesByName[name] = [];
      }

      // create directive if it does not exists

      var $compileProvider = getCompileProvider();
      $compileProvider.directive(name, directiveFactory);
      var directives = $injector.get(name + DIRECTIVE_SUFFIX);
      directiveList.push(directives[directives.length - 1]);
      bsInjular.indexByDirectiveName[name]++;
    }

    function getCompileProvider() {
      var $compileProvider = bsInjular.$compileProvider;
      if (!$compileProvider) {
        throwError(
          'Could not get $compileProvider. ' +
          'Are you sure the moduleName in the bsInjular options is correct?'
        );
      }
      return $compileProvider;
    }
  }

  function instantiateDirective($injector, directiveFactory, name) {
    // Code from $compileProvider.directive
    var directive = $injector.invoke(directiveFactory);
    if (angular.isFunction(directive)) {
      directive = { compile: valueFn(directive) };
    } else if (!directive.compile && directive.link) {
      directive.compile = valueFn(directive.link);
    }
    directive.priority = directive.priority || 0;
    directive.name = directive.name || name;
    directive.require = directive.require || (directive.controller && directive.name);
    directive.restrict = directive.restrict || 'EA';
    return directive;
  }

  function removeReplaceableDirectiveProperties(directive) {
    for (var key in directive) {
      if (key !== 'index' && key !== '$$moduleName') {
        delete directive[key];
      }
    }
  }

  function valueFn(value) {
    return function() {
      return value;
    };
  }

  function throwError(msg) {
    throw new Error('[BS-Injular] ' + msg);
  }
})(window.___browserSync___);
<|endoftext|>


<commit_before>'use strict';

const crypto = require('crypto');
const prompt = require('souffleur');
const rp = require('minimal-request-promise');
const fbReply = require('./reply');
const fbParse = require('./parse');
const validateFbRequestIntegrity = require('./validate-integrity');
const color = require('../console-colors');

module.exports = function fbSetup(api, bot, logError, optionalParser, optionalResponder) {
  let parser = optionalParser || fbParse;
  let responder = optionalResponder || fbReply;

  api.get('/facebook', request => {
    if (request.queryString['hub.verify_token'] === request.env.facebookVerifyToken)
      return request.queryString['hub.challenge'];

    logError(`Facebook can't verify the token. It expected '${request.env.facebookVerifyToken}', but got '${request.queryString['hub.verify_token']}'. Make sure you are using the same token you set in 'facebookVerifyToken' stage env variable.`);
    return 'Error';
  }, {success: {contentType: 'text/plain'}});

  api.post('/facebook', request => {
    // We are doing verification if FB Secret exist in env because we don't want to break old bots that forgot to add it
    if (request.env.facebookAppSecret && !validateFbRequestIntegrity(request))
      return Promise.reject('X-Hub-Signatures does not match');

    let arr = [].concat.apply([], request.body.entry.map(entry => entry.messaging));
    let fbHandle = parsedMessage => {
      if (parsedMessage) {
        var recipient = parsedMessage.sender;

        return Promise.resolve(parsedMessage).then(parsedMessage => bot(parsedMessage, request))
          .then(botResponse => responder(recipient, botResponse, request.env.facebookAccessToken))
          .catch(logError);
      }
    };

    return Promise.all(arr.map(message => parser(message)).map(fbHandle))
      .then(() => 'ok');
  });

  api.addPostDeployStep('facebook', (options, lambdaDetails, utils) => {
    return utils.Promise.resolve().then(() => {
      return utils.apiGatewayPromise.getStagePromise({
        restApiId: lambdaDetails.apiId,
        stageName: lambdaDetails.alias
      }).then(data => {
        if (options['configure-fb-bot']) {
          let token, pageAccessToken;

          utils.Promise.promisifyAll(crypto);

          return utils.Promise.resolve().then(() => {
            if (data.variables && data.variables.facebookVerifyToken)
              return data.variables.facebookVerifyToken;

            return crypto.randomBytesAsync(8);
          })
          .then(rawToken => {
            token = rawToken.toString('base64').replace(/[^A-Za-z0-9]/g, '');
            return utils.apiGatewayPromise.createDeploymentPromise({
              restApiId: lambdaDetails.apiId,
              stageName: lambdaDetails.alias,
              variables: {
                facebookVerifyToken: token
              }
            });
          })
          .then(() => {
            console.log(`\n\n${color.green}Facebook Messenger setup${color.reset}\n`);
            console.log(`\nFollowing info is required for the setup, for more info check the documentation.\n`);
            console.log(`\nYour webhook URL is: ${color.cyan}${lambdaDetails.apiUrl}/facebook${color.reset}\n`);
            console.log(`Your verify token is: ${color.cyan}${token}${color.reset}\n`);

            return prompt(['Facebook page access token', 'Facebook App Secret']);
          })
          .then(results => {
            console.log('\n');
            pageAccessToken = results['Facebook page access token'];
            const deployment = {
              restApiId: lambdaDetails.apiId,
              stageName: lambdaDetails.alias,
              variables: {
                facebookAccessToken: pageAccessToken,
                facebookAppSecret: results['Facebook App Secret']
              }
            };

            return utils.apiGatewayPromise.createDeploymentPromise(deployment);
          })
          .then(() => rp.post(`https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${pageAccessToken}`));
        }

        if (data.variables.facebookAccessToken && !data.variables.facebookAppSecret)
          return console.log(`\n${color.yellow}Deprecation warning:${color.reset} Your bot is not using facebook payload validation. Please re-run ${color.yellow}claudia update${color.reset} command with ${color.yellow}--configure-fb-bot${color.reset} flag to set the facebook app secret and enable payload validation. This will become mandatory in the next major version. See https://github.com/claudiajs/claudia-bot-builder/blob/master/docs/API.md#message-verification for more information.\n\n`);
      });
    })
      .then(() => `${lambdaDetails.apiUrl}/facebook`);
  });
};
<commit_msg>Temporary remove fb security validation (#27)<commit_after>'use strict';

const crypto = require('crypto');
const prompt = require('souffleur');
const rp = require('minimal-request-promise');
const fbReply = require('./reply');
const fbParse = require('./parse');
const color = require('../console-colors');

module.exports = function fbSetup(api, bot, logError, optionalParser, optionalResponder) {
  let parser = optionalParser || fbParse;
  let responder = optionalResponder || fbReply;

  api.get('/facebook', request => {
    if (request.queryString['hub.verify_token'] === request.env.facebookVerifyToken)
      return request.queryString['hub.challenge'];

    logError(`Facebook can't verify the token. It expected '${request.env.facebookVerifyToken}', but got '${request.queryString['hub.verify_token']}'. Make sure you are using the same token you set in 'facebookVerifyToken' stage env variable.`);
    return 'Error';
  }, {success: {contentType: 'text/plain'}});

  api.post('/facebook', request => {
    let arr = [].concat.apply([], request.body.entry.map(entry => entry.messaging));
    let fbHandle = parsedMessage => {
      if (parsedMessage) {
        var recipient = parsedMessage.sender;

        return Promise.resolve(parsedMessage).then(parsedMessage => bot(parsedMessage, request))
          .then(botResponse => responder(recipient, botResponse, request.env.facebookAccessToken))
          .catch(logError);
      }
    };

    return Promise.all(arr.map(message => parser(message)).map(fbHandle))
      .then(() => 'ok');
  });

  api.addPostDeployStep('facebook', (options, lambdaDetails, utils) => {
    return utils.Promise.resolve().then(() => {
      return utils.apiGatewayPromise.getStagePromise({
        restApiId: lambdaDetails.apiId,
        stageName: lambdaDetails.alias
      }).then(data => {
        if (options['configure-fb-bot']) {
          let token, pageAccessToken;

          utils.Promise.promisifyAll(crypto);

          return utils.Promise.resolve().then(() => {
            if (data.variables && data.variables.facebookVerifyToken)
              return data.variables.facebookVerifyToken;

            return crypto.randomBytesAsync(8);
          })
          .then(rawToken => {
            token = rawToken.toString('base64').replace(/[^A-Za-z0-9]/g, '');
            return utils.apiGatewayPromise.createDeploymentPromise({
              restApiId: lambdaDetails.apiId,
              stageName: lambdaDetails.alias,
              variables: {
                facebookVerifyToken: token
              }
            });
          })
          .then(() => {
            console.log(`\n\n${color.green}Facebook Messenger setup${color.reset}\n`);
            console.log(`\nFollowing info is required for the setup, for more info check the documentation.\n`);
            console.log(`\nYour webhook URL is: ${color.cyan}${lambdaDetails.apiUrl}/facebook${color.reset}\n`);
            console.log(`Your verify token is: ${color.cyan}${token}${color.reset}\n`);

            return prompt(['Facebook page access token']);
          })
          .then(results => {
            console.log('\n');
            pageAccessToken = results['Facebook page access token'];
            const deployment = {
              restApiId: lambdaDetails.apiId,
              stageName: lambdaDetails.alias,
              variables: {
                facebookAccessToken: pageAccessToken
              }
            };

            return utils.apiGatewayPromise.createDeploymentPromise(deployment);
          })
          .then(() => rp.post(`https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${pageAccessToken}`));
        }
      });
    })
      .then(() => `${lambdaDetails.apiUrl}/facebook`);
  });
};
<|endoftext|>