<commit_before>var Chat = {
    left : null,
    right: null,
    room: null,
    date: null,
    currentDate: null,
    lastScroll: null,
    lastHeight: null,
    edit: false,

    // Chat state
    state: null,
    since: null,

    sendMessage: function(jid, muc)
    {
        var n = document.querySelector('#chat_textarea');
        var text = n.value;
        n.value = "";
        n.focus();
        MovimUtils.textareaAutoheight(n);

        localStorage.removeItem(n.dataset.jid + '_message');

        if(Chat.edit) {
            Chat.edit = false;
            Chat_ajaxCorrect(jid, encodeURIComponent(text));
        } else {
            Chat_ajaxSendMessage(jid, encodeURIComponent(text), muc);
        }
    },

    focus: function(jid)
    {
        if(jid) {
            document.querySelector('#chat_widget').dataset.jid = jid;
        }

        var textarea = document.querySelector('#chat_textarea');

        setTimeout(function() {
            var textarea = document.querySelector('#chat_textarea');
            textarea.value = localStorage.getItem(textarea.dataset.jid + '_message');
        }, 0); // Fix Me

        textarea.onkeydown = function(event) {
            if(event.keyCode == 38 && this.value == '') {
                Chat_ajaxLast(this.dataset.jid);
            } else if(event.keyCode == 40
            && (this.value == '' || Chat.edit == true)) {
                Chat.clearReplace();
            }
        };

        textarea.onkeypress = function(event) {
            if(event.keyCode == 13) {
                if(event.shiftKey) {
                    return;
                }
                Chat.state = 0;
                Chat.sendMessage(this.dataset.jid, Boolean(this.dataset.muc));

                return false;
            } else if(!Boolean(this.dataset.muc)) {
                if(Chat.state == 0 || Chat.state == 2) {
                    Chat.state = 1;
                    Chat_ajaxSendComposing(this.dataset.jid);
                    Chat.since = new Date().getTime();
                }
            }
        };

        textarea.onkeyup = function(event) {
            localStorage.setItem(this.dataset.jid + '_message', this.value);

            setTimeout(function()
            {
                var textarea = document.querySelector('#chat_textarea');

                if(textarea
                && !Boolean(textarea.dataset.muc)
                && Chat.state == 1
                && Chat.since + 5000 < new Date().getTime()) {
                    Chat.state = 2;
                    Chat_ajaxSendPaused(textarea.dataset.jid);
                }
            },5000);

            Chat.toggleAction(this.value.length);
        };

        textarea.oninput = function() {
            MovimUtils.textareaAutoheight(this);
        };

        if(document.documentElement.clientWidth > 1024) {
            document.querySelector('#chat_textarea').focus();
        }
    },
    setTextarea: function(value)
    {
        Chat.edit = true;
        var textarea = document.querySelector('#chat_textarea');
        textarea.value = value;
        MovimUtils.textareaAutoheight(textarea);

    },
    clearReplace: function()
    {
        Chat.edit = false;
        var textarea = document.querySelector('#chat_textarea');
        textarea.value = '';
        MovimUtils.textareaAutoheight(textarea);
    },
    notify : function(title, body, image)
    {
        if(document_focus == false) {
            movim_title_inc();
            movim_desktop_notification(title, body, image);
        }
    },
    empty : function()
    {
        Chat_ajaxGet();
    },
    setBubbles : function(left, right, room, date) {
        var div = document.createElement('div');

        div.innerHTML = left;
        Chat.left = div.firstChild.cloneNode(true);
        div.innerHTML = right;
        Chat.right = div.firstChild.cloneNode(true);
        div.innerHTML = room;
        Chat.room = div.firstChild.cloneNode(true);
        div.innerHTML = date;
        Chat.date = div.firstChild.cloneNode(true);

        Chat.setScrollBehaviour();
    },
    setScrollBehaviour : function() {
        var discussion = document.querySelector('#chat_widget div.contained');
        discussion.onscroll = function() {
            if(discussion.dataset.muc != true) {
                if(this.scrollTop < 1) {
                    var chat = document.querySelector('#chat_widget');
                    Chat_ajaxGetHistory(chat.dataset.jid, Chat.currentDate);
                }
            }
            Chat.lastHeight = this.clientHeight;
        };
    },
    appendMessagesWrapper : function(page, prepend) {
        Chat.currentDate = null;
        if(page) {
            var scrolled = MovimTpl.isPanelScrolled();

            var discussion = document.querySelector('#chat_widget div.contained');

            if(discussion == null) return;

            Chat.lastScroll = discussion.scrollHeight;

            for(date in page) {
                if (page[date].constructor == Array) {
                    for(id in page[date]) {
                        Chat.appendMucMessage(page[date][id]);
                    }
                } else {
                    for(speakertime in page[date]) {
                        if(!Chat.currentDate) {
                            Chat.currentDate = page[date][speakertime].published;
                        }
                        Chat.appendMessage(speakertime, page[date][speakertime], prepend);
                    }
                }

                if(date != '') {
                    Chat.appendDate(date, prepend);
                }
            }

            // Only scroll down if scroll was at the bottom before the new msg
            // => don't scroll if the user was reading previous messages
            if(scrolled && prepend !== true) {
                setTimeout(function() {
                    MovimTpl.scrollPanel();
                }, 20);
            }

            if(prepend) {
                // And we scroll where we were
                var scrollDiff = discussion.scrollHeight - Chat.lastScroll;
                discussion.scrollTop += scrollDiff;
                Chat.lastScroll = discussion.scrollHeight;
            }
        }
    },
    appendMucMessage : function(message) {
        var conversation = document.getElementById(
            MovimUtils.cleanupId(message.jidfrom + '_conversation')
        );
        //datebox = Chat.room.cloneNode(true);
        //datebox.innerHTML = date;

        //if(conversation) {
        //    conversation.appendChild(datebox);
        //}

        bubble = Chat.room.cloneNode(true);

        var p = bubble.querySelector('p.message');

        if(message.body.match(/^\/me/)) {
            p.classList.add('quote');
            message.body = message.body.substr(4);
        }

        if (message.quoted) {
            p.classList.add('quoted');
        }

        /*if (message.sticker != null) {
            MovimUtils.addClass(bubble.querySelector('p.message'), 'sticker');
            bubble.querySelector('p.message').appendChild(Chat.getStickerHtml(message.sticker));
        } else {*/
        p.innerHTML = message.body.replace(/\r\n?|\n/g, '
');
        //}

        bubble.querySelector('span.info').innerHTML = message.publishedPrepared;

        var user = bubble.querySelector('p.user');
        user.className = 'user ' + message.color;
        user.innerHTML = message.resource;
        user.onclick = function(n) {
            var textarea = document.querySelector('#chat_textarea');
            textarea.value = this.innerHTML + ', ' + textarea.value;
            textarea.focus();
        };

        if(conversation) {
            conversation.appendChild(bubble);
        }
    },
    appendMessage : function(idjidtime, data, prepend) {
        var bubble = null,
            mergeMsg = false,
            msgStack,
            refBubble;
        var jidtime = idjidtime.substring(idjidtime.indexOf('<') + 1);

        if(prepend) {
            refBubble = document.querySelector("#chat_widget .contained li:first-child");
            msgStack = document.querySelector("[data-bubble='" + jidtime + "']");
        } else {
            refBubble = document.querySelector("#chat_widget .contained li:last-child");
            var stack = document.querySelectorAll("[data-bubble='" + jidtime + "']");
            msgStack = stack[stack.length-1];
        }

        if(msgStack != null
            && msgStack.parentNode == refBubble
            && data.file === null
            && data.sticker === null
            && !MovimUtils.hasClass(refBubble.querySelector('div.bubble'), "sticker")
            && !MovimUtils.hasClass(refBubble.querySelector('div.bubble'), "file")
        ){
            bubble = msgStack.parentNode;
            mergeMsg = true;
        } else {
            if (data.session == data.jidfrom) {
                bubble = Chat.right.cloneNode(true);
                id = data.jidto + '_conversation';
            } else {
                bubble = Chat.left.cloneNode(true);
                id = data.jidfrom + '_conversation';
            }

            id = MovimUtils.cleanupId(id);

            bubble.querySelector('div.bubble').dataset.bubble = jidtime;
            bubble.querySelector('div.bubble').dataset.publishedprepared = data.publishedPrepared;
        }

        var msg = bubble.querySelector('div.bubble > div');
        var span = msg.getElementsByTagName('span')[0];
        var p = msg.getElementsByTagName('p')[0];

        // If there is already a msg in this bubble, create another div (next msg or replacement)
        if (bubble.querySelector('div.bubble p')
        && bubble.querySelector('div.bubble p').innerHTML != '') {
            msg = document.createElement("div");
            p = document.createElement("p");
            span = document.createElement("span");
            span.className = 'info';
        }

        if (data.rtl) {
            bubble.querySelector('div.bubble').setAttribute('dir', 'rtl');
        }

        if (data.body.match(/^\/me\s/)) {
            p.className = 'quote';
            data.body = data.body.substr(4);
        }

        if (data.id != null) {
            msg.setAttribute("id", data.id);
            if (data.newid != null) {
                msg.setAttribute("id", data.newid);
            }
        }

        if (data.sticker != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'sticker');
            p.appendChild(Chat.getStickerHtml(data.sticker));
        } else {
            p.innerHTML = data.body.replace(/\r\n?|\n/g, '
');
        }

        if (data.audio != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'file');
            p.appendChild(Chat.getAudioHtml(data.file));
        } else if (data.file != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'file');
            p.appendChild(Chat.getFileHtml(data.file, data.sticker));
        }

        if (data.edited) {
            span.appendChild(Chat.getEditedIcoHtml());
        }
        if (data.delivered) {
            span.appendChild(Chat.getDeliveredIcoHtml(data.delivered));
        }

        msg.appendChild(p);
        msg.appendChild(span);

        var elem = document.getElementById(data.id);
        if (elem) {
            elem.parentElement.replaceChild(msg, elem);
            mergeMsg = true;
        } else {
            if(prepend) {
                bubble.querySelector('div.bubble').insertBefore( msg, bubble.querySelector('div.bubble').firstChild );
            } else {
                bubble.querySelector('div.bubble').appendChild(msg);
            }
        }

        if(prepend){
            Chat.currentDate = data.published;

            // We prepend
            if (!mergeMsg) {
                MovimTpl.prepend("#" + id, bubble.outerHTML);
            }
        } else {
            if (!mergeMsg) {
                MovimTpl.append("#" + id, bubble.outerHTML);
            }
        }
    },
    appendDate: function(date, prepend) {
        var list = document.querySelector('#chat_widget > div ul');
        dateNode = Chat.date.cloneNode(true);
        dateNode.querySelector('p').innerHTML = date;

        if(prepend) {
            list.insertBefore(dateNode, list.firstChild);
        } else {
            list.appendChild(dateNode);
        }
    },
    getStickerHtml: function(sticker) {
        var img = document.createElement("img");
        if(sticker.url) {
            img.setAttribute("src", sticker.url);
            if(sticker.width)  img.setAttribute("width", sticker.width);
            if(sticker.height)
                img.setAttribute("height", sticker.height);
            else {
                img.setAttribute("height", "150");
            }
        }

        if(sticker.picture) {
            var a = document.createElement("a");
            a.setAttribute("href", sticker.url);
            a.setAttribute("target", "_blank");
            a.appendChild(img);
            return a;
        } else {
            return img;
        }
    },
    getAudioHtml: function(file) {
        var audio = document.createElement("audio");
        audio.setAttribute("controls", true);

        var source = document.createElement("source");
        source.setAttribute("src", file.uri);
        source.setAttribute("type", file.type);

        audio.appendChild(source);

        return audio;
    },
    getFileHtml: function(file, sticker) {
        var div = document.createElement("div");
        div.setAttribute("class", "file");

        var a = document.createElement("a");
        if(sticker == null) {
            a.innerHTML = file.name;
        }
        a.setAttribute("href", file.uri);
        a.setAttribute("target", "_blank");

        div.appendChild(a);

        var span = document.createElement("span");
        span.innerHTML = file.size;
        span.setAttribute("class", "size");

        a.appendChild(span);

        return div;
    },
    getEditedIcoHtml: function() {
        var i = document.createElement("i");
        i.setAttribute("class", "zmdi zmdi-edit");
        return i;
    },
    getDeliveredIcoHtml: function(delivered) {
        var i = document.createElement("i");
        i.setAttribute("class", "zmdi zmdi-check");
        i.setAttribute("title", delivered);
        return i;
    },
    toggleAction: function(l) {
        var send_button = document.querySelector(".chat_box span[data-jid]");
        var attachment_button = document.querySelector(".chat_box span.control:not([data-jid])");
        if(send_button && attachment_button) {
            if(l > 0){
                MovimUtils.showElement(send_button);
                MovimUtils.hideElement(attachment_button);
            } else {
                MovimUtils.showElement(attachment_button);
                MovimUtils.hideElement(send_button);
            }
        }
    }
};

MovimWebsocket.attach(function() {
    var jid = MovimUtils.urlParts().params[0];
    var room = MovimUtils.urlParts().params[1];
    if(jid) {
        MovimTpl.showPanel();

        if(room) {
            Chat_ajaxGetRoom(jid);
        } else {
            Chat_ajaxGet(jid);
            Notification.current('chat|' + jid);
        }
    }
});

if(typeof Upload != 'undefined') {
    Upload.attach(function(file) {
        var textarea = document.querySelector('#chat_textarea');

        Chat_ajaxSendMessage(textarea.dataset.jid, false, Boolean(textarea.dataset.muc), false, false, file);
    });
}

document.addEventListener('focus', function() {
    var textarea = document.querySelector('#chat_textarea');
    if(textarea) textarea.focus();
});

window.addEventListener('resize', function() {
    var discussion = document.querySelector('#chat_widget div.contained');
    if(discussion) {
        discussion.scrollTop += Chat.lastHeight - discussion.clientHeight;
        Chat.lastHeight = discussion.clientHeight;
    }
});

var state = 0;
<commit_msg>Small fix with the date separators when scrolling the history<commit_after>var Chat = {
    left : null,
    right: null,
    room: null,
    date: null,
    currentDate: null,
    lastScroll: null,
    lastHeight: null,
    edit: false,

    // Chat state
    state: null,
    since: null,

    sendMessage: function(jid, muc)
    {
        var n = document.querySelector('#chat_textarea');
        var text = n.value;
        n.value = "";
        n.focus();
        MovimUtils.textareaAutoheight(n);

        localStorage.removeItem(n.dataset.jid + '_message');

        if(Chat.edit) {
            Chat.edit = false;
            Chat_ajaxCorrect(jid, encodeURIComponent(text));
        } else {
            Chat_ajaxSendMessage(jid, encodeURIComponent(text), muc);
        }
    },

    focus: function(jid)
    {
        if(jid) {
            document.querySelector('#chat_widget').dataset.jid = jid;
        }

        var textarea = document.querySelector('#chat_textarea');

        setTimeout(function() {
            var textarea = document.querySelector('#chat_textarea');
            textarea.value = localStorage.getItem(textarea.dataset.jid + '_message');
        }, 0); // Fix Me

        textarea.onkeydown = function(event) {
            if(event.keyCode == 38 && this.value == '') {
                Chat_ajaxLast(this.dataset.jid);
            } else if(event.keyCode == 40
            && (this.value == '' || Chat.edit == true)) {
                Chat.clearReplace();
            }
        };

        textarea.onkeypress = function(event) {
            if(event.keyCode == 13) {
                if(event.shiftKey) {
                    return;
                }
                Chat.state = 0;
                Chat.sendMessage(this.dataset.jid, Boolean(this.dataset.muc));

                return false;
            } else if(!Boolean(this.dataset.muc)) {
                if(Chat.state == 0 || Chat.state == 2) {
                    Chat.state = 1;
                    Chat_ajaxSendComposing(this.dataset.jid);
                    Chat.since = new Date().getTime();
                }
            }
        };

        textarea.onkeyup = function(event) {
            localStorage.setItem(this.dataset.jid + '_message', this.value);

            setTimeout(function()
            {
                var textarea = document.querySelector('#chat_textarea');

                if(textarea
                && !Boolean(textarea.dataset.muc)
                && Chat.state == 1
                && Chat.since + 5000 < new Date().getTime()) {
                    Chat.state = 2;
                    Chat_ajaxSendPaused(textarea.dataset.jid);
                }
            },5000);

            Chat.toggleAction(this.value.length);
        };

        textarea.oninput = function() {
            MovimUtils.textareaAutoheight(this);
        };

        if(document.documentElement.clientWidth > 1024) {
            document.querySelector('#chat_textarea').focus();
        }
    },
    setTextarea: function(value)
    {
        Chat.edit = true;
        var textarea = document.querySelector('#chat_textarea');
        textarea.value = value;
        MovimUtils.textareaAutoheight(textarea);

    },
    clearReplace: function()
    {
        Chat.edit = false;
        var textarea = document.querySelector('#chat_textarea');
        textarea.value = '';
        MovimUtils.textareaAutoheight(textarea);
    },
    notify : function(title, body, image)
    {
        if(document_focus == false) {
            movim_title_inc();
            movim_desktop_notification(title, body, image);
        }
    },
    empty : function()
    {
        Chat_ajaxGet();
    },
    setBubbles : function(left, right, room, date) {
        var div = document.createElement('div');

        div.innerHTML = left;
        Chat.left = div.firstChild.cloneNode(true);
        div.innerHTML = right;
        Chat.right = div.firstChild.cloneNode(true);
        div.innerHTML = room;
        Chat.room = div.firstChild.cloneNode(true);
        div.innerHTML = date;
        Chat.date = div.firstChild.cloneNode(true);

        Chat.setScrollBehaviour();
    },
    setScrollBehaviour : function() {
        var discussion = document.querySelector('#chat_widget div.contained');
        discussion.onscroll = function() {
            if(discussion.dataset.muc != true) {
                if(this.scrollTop < 1) {
                    var chat = document.querySelector('#chat_widget');
                    Chat_ajaxGetHistory(chat.dataset.jid, Chat.currentDate);
                }
            }
            Chat.lastHeight = this.clientHeight;
        };
    },
    appendMessagesWrapper : function(page, prepend) {
        Chat.currentDate = null;
        if(page) {
            var scrolled = MovimTpl.isPanelScrolled();

            var discussion = document.querySelector('#chat_widget div.contained');

            if(discussion == null) return;

            Chat.lastScroll = discussion.scrollHeight;

            for(date in page) {
                if (page[date].constructor == Array) {
                    for(id in page[date]) {
                        Chat.appendMucMessage(page[date][id]);
                    }
                } else {
                    for(speakertime in page[date]) {
                        if(!Chat.currentDate) {
                            Chat.currentDate = page[date][speakertime].published;
                        }
                        Chat.appendMessage(speakertime, page[date][speakertime], prepend);
                    }
                }

                if(date != '') {
                    Chat.appendDate(date, prepend);
                }
            }

            // Only scroll down if scroll was at the bottom before the new msg
            // => don't scroll if the user was reading previous messages
            if(scrolled && prepend !== true) {
                setTimeout(function() {
                    MovimTpl.scrollPanel();
                }, 20);
            }

            if(prepend) {
                // And we scroll where we were
                var scrollDiff = discussion.scrollHeight - Chat.lastScroll;
                discussion.scrollTop += scrollDiff;
                Chat.lastScroll = discussion.scrollHeight;
            }
        }
    },
    appendMucMessage : function(message) {
        var conversation = document.getElementById(
            MovimUtils.cleanupId(message.jidfrom + '_conversation')
        );
        //datebox = Chat.room.cloneNode(true);
        //datebox.innerHTML = date;

        //if(conversation) {
        //    conversation.appendChild(datebox);
        //}

        bubble = Chat.room.cloneNode(true);

        var p = bubble.querySelector('p.message');

        if(message.body.match(/^\/me/)) {
            p.classList.add('quote');
            message.body = message.body.substr(4);
        }

        if (message.quoted) {
            p.classList.add('quoted');
        }

        /*if (message.sticker != null) {
            MovimUtils.addClass(bubble.querySelector('p.message'), 'sticker');
            bubble.querySelector('p.message').appendChild(Chat.getStickerHtml(message.sticker));
        } else {*/
        p.innerHTML = message.body.replace(/\r\n?|\n/g, '
');
        //}

        bubble.querySelector('span.info').innerHTML = message.publishedPrepared;

        var user = bubble.querySelector('p.user');
        user.className = 'user ' + message.color;
        user.innerHTML = message.resource;
        user.onclick = function(n) {
            var textarea = document.querySelector('#chat_textarea');
            textarea.value = this.innerHTML + ', ' + textarea.value;
            textarea.focus();
        };

        if(conversation) {
            conversation.appendChild(bubble);
        }
    },
    appendMessage : function(idjidtime, data, prepend) {
        var bubble = null,
            mergeMsg = false,
            msgStack,
            refBubble;
        var jidtime = idjidtime.substring(idjidtime.indexOf('<') + 1);

        if(prepend) {
            refBubble = document.querySelector("#chat_widget .contained li:first-child");
            msgStack = document.querySelector("[data-bubble='" + jidtime + "']");
        } else {
            refBubble = document.querySelector("#chat_widget .contained li:last-child");
            var stack = document.querySelectorAll("[data-bubble='" + jidtime + "']");
            msgStack = stack[stack.length-1];
        }

        if(msgStack != null
            && msgStack.parentNode == refBubble
            && data.file === null
            && data.sticker === null
            && !MovimUtils.hasClass(refBubble.querySelector('div.bubble'), "sticker")
            && !MovimUtils.hasClass(refBubble.querySelector('div.bubble'), "file")
        ){
            bubble = msgStack.parentNode;
            mergeMsg = true;
        } else {
            if (data.session == data.jidfrom) {
                bubble = Chat.right.cloneNode(true);
                id = data.jidto + '_conversation';
            } else {
                bubble = Chat.left.cloneNode(true);
                id = data.jidfrom + '_conversation';
            }

            id = MovimUtils.cleanupId(id);

            bubble.querySelector('div.bubble').dataset.bubble = jidtime;
            bubble.querySelector('div.bubble').dataset.publishedprepared = data.publishedPrepared;
        }

        var msg = bubble.querySelector('div.bubble > div');
        var span = msg.getElementsByTagName('span')[0];
        var p = msg.getElementsByTagName('p')[0];

        // If there is already a msg in this bubble, create another div (next msg or replacement)
        if (bubble.querySelector('div.bubble p')
        && bubble.querySelector('div.bubble p').innerHTML != '') {
            msg = document.createElement("div");
            p = document.createElement("p");
            span = document.createElement("span");
            span.className = 'info';
        }

        if (data.rtl) {
            bubble.querySelector('div.bubble').setAttribute('dir', 'rtl');
        }

        if (data.body.match(/^\/me\s/)) {
            p.className = 'quote';
            data.body = data.body.substr(4);
        }

        if (data.id != null) {
            msg.setAttribute("id", data.id);
            if (data.newid != null) {
                msg.setAttribute("id", data.newid);
            }
        }

        if (data.sticker != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'sticker');
            p.appendChild(Chat.getStickerHtml(data.sticker));
        } else {
            p.innerHTML = data.body.replace(/\r\n?|\n/g, '
');
        }

        if (data.audio != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'file');
            p.appendChild(Chat.getAudioHtml(data.file));
        } else if (data.file != null) {
            MovimUtils.addClass(bubble.querySelector('div.bubble'), 'file');
            p.appendChild(Chat.getFileHtml(data.file, data.sticker));
        }

        if (data.edited) {
            span.appendChild(Chat.getEditedIcoHtml());
        }
        if (data.delivered) {
            span.appendChild(Chat.getDeliveredIcoHtml(data.delivered));
        }

        msg.appendChild(p);
        msg.appendChild(span);

        var elem = document.getElementById(data.id);
        if (elem) {
            elem.parentElement.replaceChild(msg, elem);
            mergeMsg = true;
        } else {
            if(prepend) {
                bubble.querySelector('div.bubble').insertBefore( msg, bubble.querySelector('div.bubble').firstChild );
            } else {
                bubble.querySelector('div.bubble').appendChild(msg);
            }
        }

        if(prepend){
            Chat.currentDate = data.published;

            // We prepend
            if (!mergeMsg) {
                MovimTpl.prepend("#" + id, bubble.outerHTML);
            }
        } else {
            if (!mergeMsg) {
                MovimTpl.append("#" + id, bubble.outerHTML);
            }
        }
    },
    appendDate: function(date, prepend) {
        var list = document.querySelector('#chat_widget > div ul');
        dateNode = Chat.date.cloneNode(true);
        dateNode.dataset.value = date;
        dateNode.querySelector('p').innerHTML = date;

        var dates = list.querySelectorAll('li.date');

        if(prepend) {
            // If the date was already displayed we remove it
            if(dates.length > 0
            && dates[0].dataset.value == date) {
                dates[0].remove();
            }

            list.insertBefore(dateNode, list.firstChild);
        } else {
            list.appendChild(dateNode);
        }
    },
    getStickerHtml: function(sticker) {
        var img = document.createElement("img");
        if(sticker.url) {
            img.setAttribute("src", sticker.url);
            if(sticker.width)  img.setAttribute("width", sticker.width);
            if(sticker.height)
                img.setAttribute("height", sticker.height);
            else {
                img.setAttribute("height", "150");
            }
        }

        if(sticker.picture) {
            var a = document.createElement("a");
            a.setAttribute("href", sticker.url);
            a.setAttribute("target", "_blank");
            a.appendChild(img);
            return a;
        } else {
            return img;
        }
    },
    getAudioHtml: function(file) {
        var audio = document.createElement("audio");
        audio.setAttribute("controls", true);

        var source = document.createElement("source");
        source.setAttribute("src", file.uri);
        source.setAttribute("type", file.type);

        audio.appendChild(source);

        return audio;
    },
    getFileHtml: function(file, sticker) {
        var div = document.createElement("div");
        div.setAttribute("class", "file");

        var a = document.createElement("a");
        if(sticker == null) {
            a.innerHTML = file.name;
        }
        a.setAttribute("href", file.uri);
        a.setAttribute("target", "_blank");

        div.appendChild(a);

        var span = document.createElement("span");
        span.innerHTML = file.size;
        span.setAttribute("class", "size");

        a.appendChild(span);

        return div;
    },
    getEditedIcoHtml: function() {
        var i = document.createElement("i");
        i.setAttribute("class", "zmdi zmdi-edit");
        return i;
    },
    getDeliveredIcoHtml: function(delivered) {
        var i = document.createElement("i");
        i.setAttribute("class", "zmdi zmdi-check");
        i.setAttribute("title", delivered);
        return i;
    },
    toggleAction: function(l) {
        var send_button = document.querySelector(".chat_box span[data-jid]");
        var attachment_button = document.querySelector(".chat_box span.control:not([data-jid])");
        if(send_button && attachment_button) {
            if(l > 0){
                MovimUtils.showElement(send_button);
                MovimUtils.hideElement(attachment_button);
            } else {
                MovimUtils.showElement(attachment_button);
                MovimUtils.hideElement(send_button);
            }
        }
    }
};

MovimWebsocket.attach(function() {
    var jid = MovimUtils.urlParts().params[0];
    var room = MovimUtils.urlParts().params[1];
    if(jid) {
        MovimTpl.showPanel();

        if(room) {
            Chat_ajaxGetRoom(jid);
        } else {
            Chat_ajaxGet(jid);
            Notification.current('chat|' + jid);
        }
    }
});

if(typeof Upload != 'undefined') {
    Upload.attach(function(file) {
        var textarea = document.querySelector('#chat_textarea');

        Chat_ajaxSendMessage(textarea.dataset.jid, false, Boolean(textarea.dataset.muc), false, false, file);
    });
}

document.addEventListener('focus', function() {
    var textarea = document.querySelector('#chat_textarea');
    if(textarea) textarea.focus();
});

window.addEventListener('resize', function() {
    var discussion = document.querySelector('#chat_widget div.contained');
    if(discussion) {
        discussion.scrollTop += Chat.lastHeight - discussion.clientHeight;
        Chat.lastHeight = discussion.clientHeight;
    }
});

var state = 0;
<|endoftext|>


<commit_before>// end-meeting.js
// ends a meeting if no events have been sent to the server for a while
'use strict'

var Promise = require('promise')
const winston = require('winston')
const _ = require('underscore')

const MAX_TIME_SINCE_LAST_UTTERANCE = 5 * 60 * 1000
var pid = null
var scope = {}

// returns a list of all active meetings, or false if an error occurred.
var getActiveMeetings = function () {
  return scope.app.service('meetings').find({
    query: {
      active: true
    }
  }).then((meetings) => {
    return meetings
  }).catch((err) => {
    winston.log('error', 'Couldnt find all active meetings:', err)
    return []
  })
}

// returns an object that indicates whether the given meeting should be ended.
var isMeetingEnded = function (meeting, passedApp) {
  var app = passedApp === undefined ? scope.app : passedApp
  return app.service('utterances').find({
    query: {
      meeting: meeting,
      $sort: {endTime: -1},
      $limit: 1
    }
  }).then((lastUtterances) => {
    var waitFor
    if (lastUtterances.length === 0) {
      waitFor = app.service('meetings').get(meeting).then((meetingObject) => {
        return (new Date().getTime() - new Date(meetingObject.startTime).getTime())
      })
    } else {
      waitFor = Promise.resolve(new Date().getTime() - new Date(lastUtterances[0].endTime).getTime())
    }
    return waitFor.then((elapsedTime) => {
      var meetingShouldEnd = elapsedTime > MAX_TIME_SINCE_LAST_UTTERANCE
      winston.log('info', 'should end?:', elapsedTime, MAX_TIME_SINCE_LAST_UTTERANCE)
      winston.log('info', 'should end?:', elapsedTime > MAX_TIME_SINCE_LAST_UTTERANCE)
      return {meetingShouldEnd: meetingShouldEnd,
              meeting: meeting}
    })
  }).catch((err) => {
    winston.log('error', 'Couldnt find last utterance:', err)
    return {meetingShouldEnd: true,
            meeting: meeting}
  })
}

var maybeEndMeeting = function (context, passedApp) {
  var app = passedApp === undefined ? scope.app : passedApp
  if (context.meetingShouldEnd) {
    return app.service('meetings').patch(context.meeting, {participants: []})
              .then((patchedMeeting) => {
                winston.log('info', 'patched meeting:', patchedMeeting)
                return patchedMeeting.participants.length === 0 &&
                       patchedMeeting.active === false
              }).catch((err) => {
                winston.log('info', 'Couldnt patch meeting!', err)
                return false
              })
  } else {
    return false
  }
}

var endInactiveMeetings = function (meetings, passedApp) {
  /* if (meetings === false) {
     return {}
     } */
  var app = passedApp === undefined ? scope.app : passedApp
  return Promise.all(_.map(meetings, (meeting) => {
    return isMeetingEnded(meeting, app).then((meetingEnded) => { return maybeEndMeeting(meetingEnded, app) })
  }))
}

var monitorMeetings = function () {
  // not sure that this promise chain works the way I'd like it to
  winston.log('info', '[end-meeting-job] checking all meetings...')
  getActiveMeetings().then(endInactiveMeetings).then((endedMeetings) => {
    console.log('info', '(maybe) ended meetings:', endedMeetings)
  }).catch((err) => {
    console.log('info', 'oops:', err)
  })
}

var startMonitoringMeetings = function (app) {
  scope.app = app
  if (pid) {
    return false
  } else {
    winston.log('info', '[end-meeting-job] Starting to monitor meetings...')
    pid = setInterval(monitorMeetings, 1 * 60 * 1000)
  }
}

var stopMonitoringMeetings = function () {
  clearInterval(pid)
}

module.exports = {
  _isMeetingEnded: isMeetingEnded,
  _endInactiveMeetings: endInactiveMeetings,
  _maybeEndMeeting: maybeEndMeeting,
  startJob: startMonitoringMeetings,
  stopJob: stopMonitoringMeetings
}
<commit_msg>Debugging meeting patching<commit_after>// end-meeting.js
// ends a meeting if no events have been sent to the server for a while
'use strict'

var Promise = require('promise')
const winston = require('winston')
const _ = require('underscore')

const MAX_TIME_SINCE_LAST_UTTERANCE = 5 * 60 * 1000
var pid = null
var scope = {}

// returns a list of all active meetings, or false if an error occurred.
var getActiveMeetings = function () {
  return scope.app.service('meetings').find({
    query: {
      active: true
    }
  }).then((meetings) => {
    return meetings
  }).catch((err) => {
    winston.log('error', 'Couldnt find all active meetings:', err)
    return []
  })
}

// returns an object that indicates whether the given meeting should be ended.
var isMeetingEnded = function (meeting, passedApp) {
  winston.log('info', 'isMeetingEnded', meeting);
  var app = passedApp === undefined ? scope.app : passedApp
  return app.service('utterances').find({
    query: {
      meeting: meeting,
      $sort: {endTime: -1},
      $limit: 1
    }
  }).then((lastUtterances) => {
    var waitFor
    if (lastUtterances.length === 0) {
      waitFor = app.service('meetings').get(meeting).then((meetingObject) => {
        return (new Date().getTime() - new Date(meetingObject.startTime).getTime())
      })
    } else {
      waitFor = Promise.resolve(new Date().getTime() - new Date(lastUtterances[0].endTime).getTime())
    }
    return waitFor.then((elapsedTime) => {
      var meetingShouldEnd = elapsedTime > MAX_TIME_SINCE_LAST_UTTERANCE
      winston.log('info', 'should end?:', elapsedTime, MAX_TIME_SINCE_LAST_UTTERANCE)
      winston.log('info', 'should end?:', elapsedTime > MAX_TIME_SINCE_LAST_UTTERANCE)
      return {meetingShouldEnd: meetingShouldEnd,
              meeting: meeting}
    })
  }).catch((err) => {
    winston.log('error', 'Couldnt find last utterance:', err)
    return {meetingShouldEnd: true,
            meeting: meeting}
  })
}

var maybeEndMeeting = function (context, passedApp) {
  var app = passedApp === undefined ? scope.app : passedApp
  if (context.meetingShouldEnd) {
    winston.log('info', 'meetingShouldEnd', context.meeting)
    return app.service('meetings').patch(context.meeting, {participants: []})
              .then((patchedMeeting) => {
                winston.log('info', 'patched meeting:', patchedMeeting)
                return patchedMeeting.participants.length === 0 &&
                       patchedMeeting.active === false
              }).catch((err) => {
                winston.log('info', 'Couldnt patch meeting!', err)
                return false
              })
  } else {
    return false
  }
}

var endInactiveMeetings = function (meetings, passedApp) {
  /* if (meetings === false) {
     return {}
     } */
  var app = passedApp === undefined ? scope.app : passedApp
  return Promise.all(_.map(meetings, (meeting) => {
    return isMeetingEnded(meeting, app).then((meetingEnded) => { return maybeEndMeeting(meetingEnded, app) })
  }))
}

var monitorMeetings = function () {
  // not sure that this promise chain works the way I'd like it to
  winston.log('info', '[end-meeting-job] checking all meetings...')
  getActiveMeetings().then(endInactiveMeetings).then((endedMeetings) => {
    console.log('info', '(maybe) ended meetings:', endedMeetings)
  }).catch((err) => {
    console.log('info', 'oops:', err)
  })
}

var startMonitoringMeetings = function (app) {
  scope.app = app
  if (pid) {
    return false
  } else {
    winston.log('info', '[end-meeting-job] Starting to monitor meetings...')
    pid = setInterval(monitorMeetings, 1 * 60 * 1000)
  }
}

var stopMonitoringMeetings = function () {
  clearInterval(pid)
}

module.exports = {
  _isMeetingEnded: isMeetingEnded,
  _endInactiveMeetings: endInactiveMeetings,
  _maybeEndMeeting: maybeEndMeeting,
  startJob: startMonitoringMeetings,
  stopJob: stopMonitoringMeetings
}
<|endoftext|>


<commit_before>exports.getFullDate = function() {
	var date = new Date();

	var year = date.getFullYear();
	var month = date.getMonth() + 1;
	var day = date.getDate();
	var hours = date.getHours();
	var minutes = date.getMinutes();

	if(month < 10) {
		month = "0" + month;
	}
	if(day < 10) {
		day = "0" + day;
	}
	if(hours < 10) {
		hours = "0" + hours;
	}
	if(minutes < 10) {
		minutes = "0" + minutes;
	}

	var datestr = day + '/' + month + '/' + year + ' ' + hours + ':' + minutes;
	return datestr;
}

exports.getChannelID = function(channel) {
	var id;
	switch(channel) {
		case 'TF1':
			id = 1;
			break;
		case 'France 2':
			id = 2;
			break;
		case 'France 3':
			id = 3;
			break;
		case 'Canal+':
			id = 4;
			break;
		case 'Arte':
			id = 7;
			break;
		case 'M6':
			id = 6;
			break;
		case 'France 4':
			id = 14;
			break;
		case 'France 5':
			id = 5;
			break;
		case 'D8':
			id = 8;
			break;
		case 'W9':
			id = 9;
			break;
		case 'TMC':
			id = 10;
			break;
		case 'NT1':
			id = 11;
			break;
		case 'NRJ12':
			id = 12;
			break;
		case 'LCP':
			id = 13;
			break;
		case 'BFM TV':
			id = 15;
			break;
		case 'i>TELE':
			id = 16;
			break;
		case 'D17':
			id = 17;
			break;
		case 'Gulli':
			id = 18;
			break;
		case 'RTL9':
			id = 21;
			break;
		case 'AB1':
			id = 22;
			break;
		case 'Disney Channel':
			id = 23;
			break;
		case 'TV5 Monde':
			id = 25;
			break;
		case 'Vivolta':
			id = 47;
			break;
		case 'NRJ Hits':
			id = 59;
			break;
		case 'Clubbing TV':
			id = 72;
			break;
		case 'BeBlack':
			id = 78;
			break;
		case 'O Five':
			id = 79;
			break;
		case 'BFM Business':
			id = 80;
			break;
		case 'Euronews':
			id = 82;
			break;
		case 'Bloomberg':
			id = 83;
			break;
		case 'Al Jazeera':
			id = 85;
			break;
		case 'Sky News':
			id = 87;
			break;
		case 'Guysen TV':
			id = 88;
			break;
		case 'CNBC':
			id = 89;
			break;
		case 'MCE':
			id = 93;
			break;
		case 'France 24':
			id = 95;
			break;
		case 'Game One':
			id = 118;
			break;
		case 'Game One Music':
			id = 119;
			break;
		case 'Lucky Jack':
			id = 121;
			break;
		case 'Men\'s up':
			id = 122;
			break;
		case 'Nolife':
			id = 123;
			break;
		case 'Fashion TV':
			id = 131;
			break;
		case 'World Fashion':
			id = 132;
			break;
		case 'Allocine':
			id = 133;
			break;
		case 'Equidia Live':
			id = 137;
			break;
		case 'Equidia Life':
			id = 138;
			break;
		case 'Renault TV':
			id = 139;
			break;
		case 'AB Moteurs':
			id = 143;
			break;
		case 'Poker Channel':
			id = 145;
			break;
		case 'France Ô':
			id = 152;
			break;
		case 'Liberty TV':
			id = 154;
			break;
		case 'Montagne TV':
			id = 156;
			break;
		case 'Luxe.TV':
			id = 157;
			break;
		case 'Demain TV':
			id = 163;
			break;
		case 'KTO':
			id = 164;
			break;
		case 'Wild Earth':
			id = 166;
			break;
		case 'TNA':
			id = 168;
			break;
		case 'Souvenirs from Earth':
			id = 169;
			break;
		case 'Penthouse':
			id = 176;
			break;
		case 'M6 Boutique':
			id = 191;
			break;
		case 'Best of Shopping':
			id = 193;
			break;
		case 'Astro Center':
			id = 195;
			break;
		case 'Radio':
			id = 199;
			break;
		case 'C+ Cinéma':
			id = 41;
			break;
		case 'C+ Sport':
			id = 42;
			break;
		case 'C+ Décalé':
			id = 43;
			break;
		case 'C+ Family':
			id = 44;
			break;
		default:
			Ti.API.error('could not find channel ID for ' + channel);
			id = null;
			break;
	}

	return id;
}
//used to get the string equivalent of a given freebox model
exports.getModelString = function(model) {
	if(model == Model.FREEBOX_HD) {
		return 'Freebox HD';
	} else if(model == Model.FREEBOX_REVOLUTION) {
		return 'Freebox Révolution';
	} else if(model == Model.FREEBOX_PLAYER) {
		return 'Freebox Player';
	} else {
		return 'Unknow model';
	}
}

exports.capitalize = function(string) {
	return string.toLowerCase().charAt(0).toUpperCase() + string.toLowerCase().slice(1);
};

exports.getMajorVersion = function() {
	var version = Titanium.Platform.version.split(".");
	var major = parseInt(version[0], 10);

	return major;
}

exports.confCheck = function(tabGroup) {
	if(Ti.Network.networkType != Ti.Network.NETWORK_WIFI) {
		var alert = Ti.UI.createAlertDialog({
			title: I('network.message.title'),
			message: I('network.message.message', Ti.App.name) + ' (E' + Error.NETWORK + ')',
			buttonNames: [I('network.buttons.ok')]
		});

		alert.show();
	} else if(!Ti.App.Properties.getBool('hasBeenSet', false)) {
		var alert = Ti.UI.createAlertDialog({
			title: I('welcome.message.title'),
			message: I('welcome.message.message', Ti.App.name),
			buttonNames: [I('welcome.buttons.no'), I('welcome.buttons.yes')],
			cancel: 0
		});

		alert.show();

		alert.addEventListener('click', function(e) {
			if(e.index == 1) {
				var helpwin = Ti.UI.createWindow({
					url: '/views/subviews/options/options.js',
					title: I('labels.options'),
					backgroundColor: '#d8d8d8',
					barColor: '#464646'
				});

				tabGroup.tabs[3].open(helpwin);
				tabGroup.setActiveTab(3);
			}
		});
	}
}<commit_msg>Removed useless getMajorVersion<commit_after>exports.getFullDate = function() {
	var date = new Date();

	var year = date.getFullYear();
	var month = date.getMonth() + 1;
	var day = date.getDate();
	var hours = date.getHours();
	var minutes = date.getMinutes();

	if(month < 10) {
		month = "0" + month;
	}
	if(day < 10) {
		day = "0" + day;
	}
	if(hours < 10) {
		hours = "0" + hours;
	}
	if(minutes < 10) {
		minutes = "0" + minutes;
	}

	var datestr = day + '/' + month + '/' + year + ' ' + hours + ':' + minutes;
	return datestr;
}

exports.getChannelID = function(channel) {
	var id;
	switch(channel) {
		case 'TF1':
			id = 1;
			break;
		case 'France 2':
			id = 2;
			break;
		case 'France 3':
			id = 3;
			break;
		case 'Canal+':
			id = 4;
			break;
		case 'Arte':
			id = 7;
			break;
		case 'M6':
			id = 6;
			break;
		case 'France 4':
			id = 14;
			break;
		case 'France 5':
			id = 5;
			break;
		case 'D8':
			id = 8;
			break;
		case 'W9':
			id = 9;
			break;
		case 'TMC':
			id = 10;
			break;
		case 'NT1':
			id = 11;
			break;
		case 'NRJ12':
			id = 12;
			break;
		case 'LCP':
			id = 13;
			break;
		case 'BFM TV':
			id = 15;
			break;
		case 'i>TELE':
			id = 16;
			break;
		case 'D17':
			id = 17;
			break;
		case 'Gulli':
			id = 18;
			break;
		case 'RTL9':
			id = 21;
			break;
		case 'AB1':
			id = 22;
			break;
		case 'Disney Channel':
			id = 23;
			break;
		case 'TV5 Monde':
			id = 25;
			break;
		case 'Vivolta':
			id = 47;
			break;
		case 'NRJ Hits':
			id = 59;
			break;
		case 'Clubbing TV':
			id = 72;
			break;
		case 'BeBlack':
			id = 78;
			break;
		case 'O Five':
			id = 79;
			break;
		case 'BFM Business':
			id = 80;
			break;
		case 'Euronews':
			id = 82;
			break;
		case 'Bloomberg':
			id = 83;
			break;
		case 'Al Jazeera':
			id = 85;
			break;
		case 'Sky News':
			id = 87;
			break;
		case 'Guysen TV':
			id = 88;
			break;
		case 'CNBC':
			id = 89;
			break;
		case 'MCE':
			id = 93;
			break;
		case 'France 24':
			id = 95;
			break;
		case 'Game One':
			id = 118;
			break;
		case 'Game One Music':
			id = 119;
			break;
		case 'Lucky Jack':
			id = 121;
			break;
		case 'Men\'s up':
			id = 122;
			break;
		case 'Nolife':
			id = 123;
			break;
		case 'Fashion TV':
			id = 131;
			break;
		case 'World Fashion':
			id = 132;
			break;
		case 'Allocine':
			id = 133;
			break;
		case 'Equidia Live':
			id = 137;
			break;
		case 'Equidia Life':
			id = 138;
			break;
		case 'Renault TV':
			id = 139;
			break;
		case 'AB Moteurs':
			id = 143;
			break;
		case 'Poker Channel':
			id = 145;
			break;
		case 'France Ô':
			id = 152;
			break;
		case 'Liberty TV':
			id = 154;
			break;
		case 'Montagne TV':
			id = 156;
			break;
		case 'Luxe.TV':
			id = 157;
			break;
		case 'Demain TV':
			id = 163;
			break;
		case 'KTO':
			id = 164;
			break;
		case 'Wild Earth':
			id = 166;
			break;
		case 'TNA':
			id = 168;
			break;
		case 'Souvenirs from Earth':
			id = 169;
			break;
		case 'Penthouse':
			id = 176;
			break;
		case 'M6 Boutique':
			id = 191;
			break;
		case 'Best of Shopping':
			id = 193;
			break;
		case 'Astro Center':
			id = 195;
			break;
		case 'Radio':
			id = 199;
			break;
		case 'C+ Cinéma':
			id = 41;
			break;
		case 'C+ Sport':
			id = 42;
			break;
		case 'C+ Décalé':
			id = 43;
			break;
		case 'C+ Family':
			id = 44;
			break;
		default:
			Ti.API.error('could not find channel ID for ' + channel);
			id = null;
			break;
	}

	return id;
}
//used to get the string equivalent of a given freebox model
exports.getModelString = function(model) {
	if(model == Model.FREEBOX_HD) {
		return 'Freebox HD';
	} else if(model == Model.FREEBOX_REVOLUTION) {
		return 'Freebox Révolution';
	} else if(model == Model.FREEBOX_PLAYER) {
		return 'Freebox Player';
	} else {
		return 'Unknow model';
	}
}

exports.capitalize = function(string) {
	return string.toLowerCase().charAt(0).toUpperCase() + string.toLowerCase().slice(1);
};

exports.confCheck = function(tabGroup) {
	if(Ti.Network.networkType != Ti.Network.NETWORK_WIFI) {
		var alert = Ti.UI.createAlertDialog({
			title: I('network.message.title'),
			message: I('network.message.message', Ti.App.name) + ' (E' + Error.NETWORK + ')',
			buttonNames: [I('network.buttons.ok')]
		});

		alert.show();
	} else if(!Ti.App.Properties.getBool('hasBeenSet', false)) {
		var alert = Ti.UI.createAlertDialog({
			title: I('welcome.message.title'),
			message: I('welcome.message.message', Ti.App.name),
			buttonNames: [I('welcome.buttons.no'), I('welcome.buttons.yes')],
			cancel: 0
		});

		alert.show();

		alert.addEventListener('click', function(e) {
			if(e.index == 1) {
				var helpwin = Ti.UI.createWindow({
					url: '/views/subviews/options/options.js',
					title: I('labels.options'),
					backgroundColor: '#d8d8d8',
					barColor: '#464646'
				});

				tabGroup.tabs[3].open(helpwin);
				tabGroup.setActiveTab(3);
			}
		});
	}
}<|endoftext|>


<commit_before>var ERR = require('async-stacktrace');
var passport = require('passport');
var express = require('express');
var router = express.Router();

var logger = require('../../lib/logger');
var config = require('../../lib/config');
var csrf = require('../../lib/csrf');
var sqldb = require('../../lib/sqldb');

// FIXME: do we need "all" below for both "get" and "post", or just one of them?
router.all('/', function(req, res, next) {
    const authData = {
        response: res,
        failureRedirect: '/pl',
        session: false,
    };
    passport.authenticate('azuread-openidconnect', authData, function(err, user, info) {
        if (ERR(err, next)) return;
        if (!user) return next(new Error('Login failed'));

        var params = [
            user.email,       // uid
            user.displayName, // name
            null,             // uin
            'azure',          // provider
        ];
        sqldb.call('users_select_or_insert', params, (err, result) => {
            if (ERR(err, next)) return;
            var tokenData = {
                user_id: result.rows[0].user_id
            };
            var pl_authn = csrf.generateToken(tokenData, config.secretKey);
            res.cookie('pl_authn', pl_authn, {maxAge: 24 * 60 * 60 * 1000});
            res.redirect(res.locals.plainUrlPrefix);
        });
    })(req, res, next);
});

module.exports = router;
<commit_msg>add debug logging<commit_after>var ERR = require('async-stacktrace');
var passport = require('passport');
var express = require('express');
var router = express.Router();

var logger = require('../../lib/logger');
var config = require('../../lib/config');
var csrf = require('../../lib/csrf');
var sqldb = require('../../lib/sqldb');

// FIXME: do we need "all" below for both "get" and "post", or just one of them?
router.all('/', function(req, res, next) {
    const authData = {
        response: res,
        failureRedirect: '/pl',
        session: false,
    };
    passport.authenticate('azuread-openidconnect', authData, function(err, user, info) {
        if (ERR(err, next)) return;
        if (!user) return next(new Error('Login failed'));

        console.log('user', user, 'info', info);
        logger.info({user, info});
        var params = [
            user.email,       // uid
            user.displayName, // name
            null,             // uin
            'azure',          // provider
        ];
        sqldb.call('users_select_or_insert', params, (err, result) => {
            if (ERR(err, next)) return;
            var tokenData = {
                user_id: result.rows[0].user_id
            };
            var pl_authn = csrf.generateToken(tokenData, config.secretKey);
            res.cookie('pl_authn', pl_authn, {maxAge: 24 * 60 * 60 * 1000});
            res.redirect(res.locals.plainUrlPrefix);
        });
    })(req, res, next);
});

module.exports = router;
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var applicationConfiguration = require('./config/config');

// Karma configuration
module.exports = function(config) {
	config.set({
		// Frameworks to use
		frameworks: ['jasmine'],

		// List of files / patterns to load in the browser
		files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),

		// Test results reporter to use
		// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 'mocha'
		//reporters: ['progress'],
		reporters : ['mocha'],

		// Web server port
		port: 9876,

		// Enable / disable colors in the output (reporters and logs)
		colors: true,

		// Level of logging
		// Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
		logLevel: config.LOG_INFO,

		// Enable / disable watching file and executing tests whenever any file changes
		autoWatch: true,

		// Start these browsers, currently available:
		// - Chrome
		// - ChromeCanary
		// - Firefox
		// - Opera
		// - Safari (only Mac)
		// - PhantomJS
		// - IE (only Windows)
		browsers: ['Chrome'],

		// If browser does not capture in given timeout [ms], kill it
		captureTimeout: 60000,

		// Continuous Integration mode
		// If true, it capture browsers, run tests and exit
		singleRun: false
	});
};<commit_msg>Changed karma config back to PhantomJs<commit_after>'use strict';

/**
 * Module dependencies.
 */
var applicationConfiguration = require('./config/config');

// Karma configuration
module.exports = function(config) {
	config.set({
		// Frameworks to use
		frameworks: ['jasmine'],

		// List of files / patterns to load in the browser
		files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),

		// Test results reporter to use
		// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 'mocha'
		//reporters: ['progress'],
		reporters : ['mocha'],

		// Web server port
		port: 9876,

		// Enable / disable colors in the output (reporters and logs)
		colors: true,

		// Level of logging
		// Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
		logLevel: config.LOG_INFO,

		// Enable / disable watching file and executing tests whenever any file changes
		autoWatch: true,

		// Start these browsers, currently available:
		// - Chrome
		// - ChromeCanary
		// - Firefox
		// - Opera
		// - Safari (only Mac)
		// - PhantomJS
		// - IE (only Windows)
		browsers: ['PhantomJS'],

		// If browser does not capture in given timeout [ms], kill it
		captureTimeout: 60000,

		// Continuous Integration mode
		// If true, it capture browsers, run tests and exit
		singleRun: false
	});
};<|endoftext|>


<commit_before>/**
 * @module ember-flexberry
 */

import Ember from 'ember';
import FlexberryBaseComponent from './flexberry-base-component';

export default FlexberryBaseComponent.extend({
  modelController: null,

  /**
   * Route for edit form by click row
   *
   * @property editFormRoute
   * @type String
   * @default undefined
   */
  editFormRoute: undefined,

  /**
   * Service that triggers objectlistview events.
   *
   * @property objectlistviewEventsService
   * @type ObjectlistviewEvents
   */
  objectlistviewEventsService: Ember.inject.service('objectlistview-events'),

  /**
   * Flag to use creation button at toolbar.
   *
   * @property createNewButton
   * @type Boolean
   * @default false
   */
  createNewButton: false,

  /**
   * The flag to specify whether the create button is enabled.
   *
   * @property createNewButton
   * @type Boolean
   * @default true
   */
  enableCreateNewButton: true,

  /**
   * Flag to use refresh button at toolbar.
   *
   * @property refreshButton
   * @type Boolean
   * @default false
   */
  refreshButton: false,

  /**
   * Flag to use delete button at toolbar.
   *
   * @property deleteButton
   * @type Boolean
   * @default false
   */
  deleteButton: false,

  /**
   * Flag to use filter button at toolbar.
   *
   * @property filterButton
   * @type Boolean
   * @default false
   */
  filterButton: false,

  /**
   * Used to specify default 'filter by any match' field text.
   *
   * @property filterText
   * @type String
   * @default null
   */
  filterText: null,

  /**
   * The flag to specify whether the delete button is enabled.
   *
   * @property deleteButton
   * @type Boolean
   * @default true
   */
  enableDeleteButton: true,

  /**
   * Name of action to send out, action triggered by click on user button.
   *
   * @property customButtonAction
   * @type String
   * @default 'customButtonAction'
   */
  customButtonAction: 'customButtonAction',

  /**
   * Handler to get custom buttons from controller.
   * It has to be closure event and return array of special structures [{ buttonName: ..., buttonAction: ..., buttonClasses: ... }, {...}, ...].
   *
   * @property customButtonsClosureEvent
   * @type Function
   * @default undefined
   */
  customButtonsClosureEvent: undefined,

  /**
   * Array of custom buttons.
   *
   * @property customButtonsArray
   * @type Array
   * @default undefined
   */
  customButtonsArray: undefined,

  colsSettingsItems: [],

  init: function() {
    this._super(...arguments);

    var componentName = this.get('componentName');
    if (this.get('deleteButton') === true && !componentName) {
      throw new Error('Name of flexberry-objectlictview component was not defined.');
    }

    this.get('objectlistviewEventsService').on('olvRowSelected', this, this._rowSelected);
    this.get('objectlistviewEventsService').on('olvRowsDeleted', this, this._rowsDeleted);

    let customButton = this.get('customButtonsClosureEvent');
    if (customButton && typeof (customButton) === 'function') {
      let customButtonsResult = customButton();
      this.set('customButtonsArray', customButtonsResult);
    }

    let listUserSettings = this.modelController.model.listUserSettings;
    if ('DEFAULT' in listUserSettings) {
      delete listUserSettings.DEFAULT;
    }

    let listNamedSettings = [];
    for (let nameSetting in listUserSettings) {
      listNamedSettings[listNamedSettings.length] = nameSetting;
    }

    this.colsSettingsItems = [{
      icon: 'dropdown icon',
      iconAlignment: 'right',
      title: '',
      items: [{
        icon: 'table icon',
        iconAlignment: 'left',
        title: 'Создать настройку'
      }]
    }];
    let items = this.colsSettingsItems[0].items;
    if (listNamedSettings.length > 0) {
      let menus = [
        { name: 'use', title: 'Применить', icon: 'checkmark box' },
        { name: 'edit', title: 'Редактировать', icon: 'setting' },
        { name: 'remove', title: 'Удалить', icon: 'remove' }
      ];
      for (let menu in menus) {
        let submenu = { icon: 'angle right icon', iconAlignment: 'right', title: menus[menu].title, items: [] };
        let icon = menus[menu].icon + ' icon';
        for (let i = 0; i < listNamedSettings.length; i++) {
          let subSubmenu = { title: listNamedSettings[i], icon: icon, iconAlignment: 'left'};
          submenu.items[submenu.items.length] = subSubmenu;
        }

        items[items.length] = submenu;
      }
    }
    items[items.length] = {
        icon: 'remove circle icon',
        iconAlignment: 'left',
        title: 'Сбросить настройку'
      };
  },

  /**
   * Implementation of component's teardown.
   *
   * @method willDestroy
   */
  willDestroy() {
    this.get('objectlistviewEventsService').off('olvRowSelected', this, this._rowSelected);
    this.get('objectlistviewEventsService').off('olvRowsDeleted', this, this._rowsDeleted);
    this._super(...arguments);
  },

  actions: {
    refresh: function() {
      this.get('modelController').send('refreshList');
    },
    createNew: function() {
      let editFormRoute = this.get('editFormRoute');
      let modelController = this.get('modelController');
      modelController.transitionToRoute(editFormRoute + '.new');
    },

    /**
     * Delete selected rows.
     *
     * @method delete
     */
    delete: function() {
      var componentName = this.get('componentName');
      this.get('objectlistviewEventsService').deleteRowsTrigger(componentName, true);
    },

    /**
     * Filters the content by "Filter by any match" field value.
     *
     * @method filterByAnyMatch
     */
    filterByAnyMatch: function() {
      var componentName = this.get('componentName');
      this.get('objectlistviewEventsService').filterByAnyMatchTrigger(componentName, this.get('filterByAnyMatchText'));
    },

    /**
     * Remove filter from url.
     *
     * @method removeFilter
     * @public
     */
    removeFilter: function() {
      this.set('filterText', null);
    },

    customButtonAction: function(actionName) {
      this.sendAction('customButtonAction', actionName);
    },

    showConfigDialog: function() {
      this.get('modelController').send('showConfigDialog');
    },

    onMenuItemClick: function (e) {
      let iTags=Ember.$(e.currentTarget).find('I');
      let namedSetingSpans = Ember.$(e.currentTarget).find('SPAN');
      if (iTags.length <= 0 || namedSetingSpans.length <= 0) {
        return;
      }
      let className = iTags.get(0).className;
      let namedSeting = namedSetingSpans.get(0).innerText;
      switch (className) {
        case 'table icon':
          alert('Configure ' + namedSeting);
          this.sendAction('showConfigDialog', e);
          break;
        case 'checkmark box icon':
          alert('Use ' + namedSeting);
          break;
        case 'setting icon':
          alert('Edit ' + namedSeting);
          break;
        case 'remove icon':
          alert('Remove ' + namedSeting);
          break;
        case 'remove circle icon':
          alert('Remove default');
          break;
      }
    }

  },

  /**
   * Flag shows enable-state of delete button.
   * If there are selected rows button is enabled. Otherwise - not.
   *
   * @property isDeleteButtonEnabled
   * @type Boolean
   * @default false
   */
  isDeleteButtonEnabled: false,

  /**
   * Stores the text from "Filter by any match" input field.
   *
   * @property filterByAnyMatchText
   * @type String
   * @default null
   */
  filterByAnyMatchText: Ember.computed.oneWay('filterText'),

  /**
   * Event handler for "row has been selected" event in objectlistview.
   *
   * @method _rowSelected
   * @private
   *
   * @param {String} componentName The name of objectlistview component.
   * @param {Model} record The model corresponding to selected row in objectlistview.
   * @param {Integer} count Count of selected rows in objectlistview.
   */
  _rowSelected: function(componentName, record, count) {
    if (componentName === this.get('componentName')) {
      this.set('isDeleteButtonEnabled', count > 0 && this.get('enableDeleteButton'));
    }
  }
});
<commit_msg>Style ++<commit_after>/**
 * @module ember-flexberry
 */

import Ember from 'ember';
import FlexberryBaseComponent from './flexberry-base-component';

export default FlexberryBaseComponent.extend({
  modelController: null,

  /**
   * Route for edit form by click row
   *
   * @property editFormRoute
   * @type String
   * @default undefined
   */
  editFormRoute: undefined,

  /**
   * Service that triggers objectlistview events.
   *
   * @property objectlistviewEventsService
   * @type ObjectlistviewEvents
   */
  objectlistviewEventsService: Ember.inject.service('objectlistview-events'),

  /**
   * Flag to use creation button at toolbar.
   *
   * @property createNewButton
   * @type Boolean
   * @default false
   */
  createNewButton: false,

  /**
   * The flag to specify whether the create button is enabled.
   *
   * @property createNewButton
   * @type Boolean
   * @default true
   */
  enableCreateNewButton: true,

  /**
   * Flag to use refresh button at toolbar.
   *
   * @property refreshButton
   * @type Boolean
   * @default false
   */
  refreshButton: false,

  /**
   * Flag to use delete button at toolbar.
   *
   * @property deleteButton
   * @type Boolean
   * @default false
   */
  deleteButton: false,

  /**
   * Flag to use filter button at toolbar.
   *
   * @property filterButton
   * @type Boolean
   * @default false
   */
  filterButton: false,

  /**
   * Used to specify default 'filter by any match' field text.
   *
   * @property filterText
   * @type String
   * @default null
   */
  filterText: null,

  /**
   * The flag to specify whether the delete button is enabled.
   *
   * @property deleteButton
   * @type Boolean
   * @default true
   */
  enableDeleteButton: true,

  /**
   * Name of action to send out, action triggered by click on user button.
   *
   * @property customButtonAction
   * @type String
   * @default 'customButtonAction'
   */
  customButtonAction: 'customButtonAction',

  /**
   * Handler to get custom buttons from controller.
   * It has to be closure event and return array of special structures [{ buttonName: ..., buttonAction: ..., buttonClasses: ... }, {...}, ...].
   *
   * @property customButtonsClosureEvent
   * @type Function
   * @default undefined
   */
  customButtonsClosureEvent: undefined,

  /**
   * Array of custom buttons.
   *
   * @property customButtonsArray
   * @type Array
   * @default undefined
   */
  customButtonsArray: undefined,

  colsSettingsItems: [],

  init: function() {
    this._super(...arguments);

    var componentName = this.get('componentName');
    if (this.get('deleteButton') === true && !componentName) {
      throw new Error('Name of flexberry-objectlictview component was not defined.');
    }

    this.get('objectlistviewEventsService').on('olvRowSelected', this, this._rowSelected);
    this.get('objectlistviewEventsService').on('olvRowsDeleted', this, this._rowsDeleted);

    let customButton = this.get('customButtonsClosureEvent');
    if (customButton && typeof (customButton) === 'function') {
      let customButtonsResult = customButton();
      this.set('customButtonsArray', customButtonsResult);
    }

    let listUserSettings = this.modelController.model.listUserSettings;
    if ('DEFAULT' in listUserSettings) {
      delete listUserSettings.DEFAULT;
    }

    let listNamedSettings = [];
    for (let nameSetting in listUserSettings) {
      listNamedSettings[listNamedSettings.length] = nameSetting;
    }

    this.colsSettingsItems = [{
      icon: 'dropdown icon',
      iconAlignment: 'right',
      title: '',
      items: [{
        icon: 'table icon',
        iconAlignment: 'left',
        title: 'Создать настройку'
      }]
    }];
    let items = this.colsSettingsItems[0].items;
    if (listNamedSettings.length > 0) {
      let menus = [
        { name: 'use', title: 'Применить', icon: 'checkmark box' },
        { name: 'edit', title: 'Редактировать', icon: 'setting' },
        { name: 'remove', title: 'Удалить', icon: 'remove' }
      ];
      for (let menu in menus) {
        let submenu = { icon: 'angle right icon', iconAlignment: 'right', title: menus[menu].title, items: [] };
        let icon = menus[menu].icon + ' icon';
        for (let i = 0; i < listNamedSettings.length; i++) {
          let subSubmenu = { title: listNamedSettings[i], icon: icon, iconAlignment: 'left'};
          submenu.items[submenu.items.length] = subSubmenu;
        }

        items[items.length] = submenu;
      }
    }

    items[items.length] = {
        icon: 'remove circle icon',
        iconAlignment: 'left',
        title: 'Сбросить настройку'
      };
  },

  /**
   * Implementation of component's teardown.
   *
   * @method willDestroy
   */
  willDestroy() {
    this.get('objectlistviewEventsService').off('olvRowSelected', this, this._rowSelected);
    this.get('objectlistviewEventsService').off('olvRowsDeleted', this, this._rowsDeleted);
    this._super(...arguments);
  },

  actions: {
    refresh: function() {
      this.get('modelController').send('refreshList');
    },
    createNew: function() {
      let editFormRoute = this.get('editFormRoute');
      let modelController = this.get('modelController');
      modelController.transitionToRoute(editFormRoute + '.new');
    },

    /**
     * Delete selected rows.
     *
     * @method delete
     */
    delete: function() {
      var componentName = this.get('componentName');
      this.get('objectlistviewEventsService').deleteRowsTrigger(componentName, true);
    },

    /**
     * Filters the content by "Filter by any match" field value.
     *
     * @method filterByAnyMatch
     */
    filterByAnyMatch: function() {
      var componentName = this.get('componentName');
      this.get('objectlistviewEventsService').filterByAnyMatchTrigger(componentName, this.get('filterByAnyMatchText'));
    },

    /**
     * Remove filter from url.
     *
     * @method removeFilter
     * @public
     */
    removeFilter: function() {
      this.set('filterText', null);
    },

    customButtonAction: function(actionName) {
      this.sendAction('customButtonAction', actionName);
    },

    showConfigDialog: function() {
      this.get('modelController').send('showConfigDialog');
    },

    onMenuItemClick: function (e) {
      let iTags = Ember.$(e.currentTarget).find('I');
      let namedSetingSpans = Ember.$(e.currentTarget).find('SPAN');
      if (iTags.length <= 0 || namedSetingSpans.length <= 0) {
        return;
      }

      let className = iTags.get(0).className;
      let namedSeting = namedSetingSpans.get(0).innerText;
      switch (className) {
        case 'table icon':
          alert('Configure ' + namedSeting);
          this.sendAction('showConfigDialog', e);
          break;
        case 'checkmark box icon':
          alert('Use ' + namedSeting);
          break;
        case 'setting icon':
          alert('Edit ' + namedSeting);
          break;
        case 'remove icon':
          alert('Remove ' + namedSeting);
          break;
        case 'remove circle icon':
          alert('Remove default');
          break;
      }
    }

  },

  /**
   * Flag shows enable-state of delete button.
   * If there are selected rows button is enabled. Otherwise - not.
   *
   * @property isDeleteButtonEnabled
   * @type Boolean
   * @default false
   */
  isDeleteButtonEnabled: false,

  /**
   * Stores the text from "Filter by any match" input field.
   *
   * @property filterByAnyMatchText
   * @type String
   * @default null
   */
  filterByAnyMatchText: Ember.computed.oneWay('filterText'),

  /**
   * Event handler for "row has been selected" event in objectlistview.
   *
   * @method _rowSelected
   * @private
   *
   * @param {String} componentName The name of objectlistview component.
   * @param {Model} record The model corresponding to selected row in objectlistview.
   * @param {Integer} count Count of selected rows in objectlistview.
   */
  _rowSelected: function(componentName, record, count) {
    if (componentName === this.get('componentName')) {
      this.set('isDeleteButtonEnabled', count > 0 && this.get('enableDeleteButton'));
    }
  }
});
<|endoftext|>


<commit_before>import shortid from 'shortid'

const uid = {
  gen: shortid.generate(),
}

export default uid
<commit_msg>fix: fix uid gen error<commit_after>import shortid from 'shortid'

const uid = {
  gen: shortid.generate,
}

export default uid
<|endoftext|>


<commit_before>"use strict";

const
    expect = require("code").expect,
    fs = require("fs"),
    path = require("path"),
    utils = require("./lib/utils.js");

function makeTest(prefix, file, destExtension, options)
{
    const
        srcName = path.basename(file),
        baseName = path.basename(file, path.extname(file)),
        fixture = `${prefix}/src/${srcName}`,
        dest = `${prefix}/dest/${baseName}${destExtension}`,
        output = `test/fixtures/${prefix}/dest`;

    it(baseName.replace(/-/g, " "), () =>
    {
        const
            baseOptions = {
                ignoreWarnings: !prefix.startsWith("warnings"),
                output
            },
            compileOptions = Object.assign({}, baseOptions, utils.setCompilerOptions(options, srcName)),
            code = utils.compiledFixture(fixture, compileOptions).code;

        expect(code).to.equal(utils.readFixture(dest));
    });
}

function makeDescribe(title, prefix, destExtension, options)
{
    describe(title, () =>
    {
        const files = fs.readdirSync(`test/fixtures/${prefix}/src`);

        for (let file of files)
            makeTest(prefix, file, destExtension, options);
    });
}

makeDescribe("Javascript code generation", "js-nodes", ".js");
makeDescribe("Objective-J code generation", "objj-nodes", ".js");
<commit_msg>options.output should not be set<commit_after>"use strict";

const
    expect = require("code").expect,
    fs = require("fs"),
    path = require("path"),
    utils = require("./lib/utils.js");

function makeTest(prefix, file, destExtension, options)
{
    const
        srcName = path.basename(file),
        baseName = path.basename(file, path.extname(file)),
        fixture = `${prefix}/src/${srcName}`,
        dest = `${prefix}/dest/${baseName}${destExtension}`;

    it(baseName.replace(/-/g, " "), () =>
    {
        const
            baseOptions = {
                ignoreWarnings: !prefix.startsWith("warnings"),
            },
            compileOptions = Object.assign({}, baseOptions, utils.setCompilerOptions(options, srcName)),
            code = utils.compiledFixture(fixture, compileOptions).code;

        expect(code).to.equal(utils.readFixture(dest));
    });
}

function makeDescribe(title, prefix, destExtension, options)
{
    describe(title, () =>
    {
        const files = fs.readdirSync(`test/fixtures/${prefix}/src`);

        for (let file of files)
            makeTest(prefix, file, destExtension, options);
    });
}

makeDescribe("Javascript code generation", "js-nodes", ".js");
makeDescribe("Objective-J code generation", "objj-nodes", ".js");
<|endoftext|>


<commit_before>import _ from 'lodash'
import EventEmitter2 from 'eventemitter2'

class WorkflowEngine extends EventEmitter2 {
  constructor(flows, stateManager, options) {
    super()

    this.flows = flows
    this.stateManager = stateManager
    this.defaultFlow = _.get(options, 'defaultFlow') || 'main'
  }

  /**
   * Process a new incoming message from the user.
   * This will execute and run the flow until the flow ends or gets paused by user input
   * @param  {string} stateId The Id of the state.
   *                          This is usually unique per user/group/channel, depending on the platform.
   * @param  {BPIncomingEvent} event   The incoming event (message)
   * @return {Promise<State>}         Returns a promise that resolves with the new state
   *                                  when the flow is done processing
   */
  async processMessage(stateId, event) {}

  async _getOrCreateContext(stateId) {
    let state = await this._getContext(stateId)

    if (!state) {
      const flow = this._findFlow(this.defaultFlow)

      const state = {
        currentFlow: flow,
        node: flow.startNode,
        flowStack: [flow.startNode]
      }

      await this._setContext(stateId, state)
    }

    return state
  }

  _getContext(stateId) {
    return this.stateManager.getState(stateId)
  }

  _setContext(stateId, state) {
    return this.stateManager.setState(stateId, state)
  }

  async _processNode(stateId, nodeName, event) {}

  async _transitionToNextNodes(node, userState, stateId, event) {}

  _findNode(name) {}

  _findFlow(flowName) {}
}
<commit_msg>Implemented processMessage<commit_after>import _ from 'lodash'
import EventEmitter2 from 'eventemitter2'

const loggerShim = { debug: () => {} }

class WorkflowEngine extends EventEmitter2 {
  constructor(flows, stateManager, options, logger = loggerShim) {
    super()

    this.logger = logger
    this.flows = flows
    this.stateManager = stateManager
    this.defaultFlow = _.get(options, 'defaultFlow') || 'main'
  }

  /**
   * Process a new incoming message from the user.
   * This will execute and run the flow until the flow ends or gets paused by user input
   * @param  {string} stateId The Id of the state.
   *                          This is usually unique per user/group/channel, depending on the platform.
   * @param  {BPIncomingEvent} event   The incoming event (message)
   * @return {Promise<State>}         Returns a promise that resolves with the new state
   *                                  when the flow is done processing
   */
  async processMessage(stateId, event) {
    let context = this._getOrCreateContext(stateId)
    let state = null

    if (!context.currentFlow) {
      throw new Error('Expected currentFlow to be defined for stateId=' + stateId)
    }

    const catchAllOnReceive = _.get(context, 'currentFlow.catchAll.onReceive')
    if (catchAllOnReceive) {
      state = await this._executeInstructions(context)
    } else {
      state = await this.stateManager.getState(stateId)
    }

    // If there's a 'next' defined in catchAll, this will try to match any condition and if it is matched it
    // will run the node defined in the next instead of the current context node
    const catchAllNext = _.get(context, 'currentFlow.catchAll.next')
    if (catchAllNext) {
      for (let i = 0; i < catchAllNext.length; i++) {
        return await this._processNode(stateId, catchAllNext[i].node, event)
      }
    }

    return await this._processNode(stateId, context.node, event)
  }

  async _getOrCreateContext(stateId) {
    let state = await this._getContext(stateId)

    if (!state) {
      const flow = this._findFlow(this.defaultFlow)

      if (!flow) {
        throw new Error(`Could not find the default flow "${this.defaultFlow}"`)
      }

      const state = {
        currentFlow: flow,
        node: flow.startNode,
        flowStack: [flow.startNode]
      }

      await this._setContext(stateId, state)
    }

    return state
  }

  _getContext(stateId) {
    return this.stateManager.getState(stateId)
  }

  _setContext(stateId, state) {
    return this.stateManager.setState(stateId, state)
  }

  async _processNode(stateId, nodeName, event) {}

  async _transitionToNextNodes(node, userState, stateId, event) {}

  _findNode(name) {}

  _findFlow(flowName) {}

  _trace(message, state) {
    this.logger.debug(message)
  }
}
<|endoftext|>