<commit_before>/* global browser, libBackground, messenger, messageHeader */

"use strict";

function disableSymGroupingMenuitem(group) {
    browser.menus.update("rspamdSpamnessSymbolPopupGroup", {enabled: !group});
    browser.menus.update("rspamdSpamnessSymbolPopupUngroup", {enabled: group});
}
function disableSymOrderMenuitem(order) {
    browser.menus.update("rspamdSpamnessSymbolPopupSortByName", {enabled: (order !== "name")});
    browser.menus.update("rspamdSpamnessSymbolPopupSortByScore", {enabled: (order !== "score")});
}
function groupSymbols(group) {
    disableSymGroupingMenuitem(group);
    browser.popup.disableSymGroupingMenuitem(group);
    browser.storage.local.set({"headers-group_symbols": group});
    messageHeader.updateHeaders();
}
function sortSymbols(order) {
    disableSymOrderMenuitem(order);
    browser.popup.disableSymOrderMenuitem(order);
    browser.storage.local.set({"headers-symbols_order": order});
    messageHeader.updateHeaders();
}

async function moveMessage(buttonId) {
    let ids = [];
    let message = null;
    const tabs = await browser.tabs.query({active: true, currentWindow: true});
    if (tabs[0].mailTab) {
        // The tab is a Thunderbird 3-pane tab
        const messageList = await browser.mailTabs.getSelectedMessages();
        if (!messageList.messages.length) return;
        ids = messageList.messages.map((msg) => msg.id);
        [message] = messageList.messages;
    } else {
        // The tab is not a Thunderbird 3-pane tab, presumable a message pane tab
        message = await browser.messageDisplay.getDisplayedMessage(tabs[0].id);
        ids = [message.id];
    }
    const {accountId} = message.folder;
    const trainFolder = {
        rspamdSpamnessButtonHam: "TrainHam",
        rspamdSpamnessButtonSpam: "TrainSpam"
    };
    const {account, path} = await libBackground.getDestination(accountId, trainFolder[buttonId]);
    if (!account) {
        libBackground.displayNotification("spamness.alertText.destinationFolderAccountIsNotSet");
        return;
    }
    if (!path) {
        libBackground.displayNotification("spamness.alertText.destinationFolderLocationIsNotSet");
        return;
    }
    const folders = path.trim().replace(/(^\/|\/$)/g, "").split("/");
    const destination = folders.reduce(function (prev, curr, i, arr) {
        const folder = (i) ? prev.subFolders : prev.folders;
        const subFolder =
            folder.find((f) => f.path.replace(/.*[/]/, "") === decodeURIComponent(curr) || f.name === curr);
        // Break loop by mutating iterated copy
        if (!subFolder) arr.splice(1);
        return subFolder;
    }, account);

    if (typeof destination === "undefined") {
        libBackground.displayNotification("spamness.alertText.destinationFolderNotFound");
        return;
    }

    browser.storage.local.get(["trainingButtonHam-defaultAction", "trainingButtonSpam-defaultAction"])
        .then((localStorage) => {
            const action = (buttonId === "rspamdSpamnessButtonHam" &&
                    (localStorage["trainingButtonHam-defaultAction"] === "copy") ||
                buttonId === "rspamdSpamnessButtonSpam" &&
                    (localStorage["trainingButtonSpam-defaultAction"] === "copy"))
                ? "copy"
                : "move";
            browser.messages[action](ids, destination).catch(function (error) {
                libBackground.error(error);
                if ((/^Unexpected error (copy|mov)ing messages: 2147500037$/).test(error.message)) {
                    libBackground.displayNotification(
                        "spamness.alertText.error_2147500037_workaround",
                        error.message + "\n\n"
                    );
                }
            });
        });
}
function addTrainButtonsListener(windowId) {
    ["rspamdSpamnessButtonHam", "rspamdSpamnessButtonSpam"]
        .forEach(function (targetId) {
            browser.trainButtons.onButtonCommand.addListener((buttonId) => {
                moveMessage(buttonId);
            }, targetId, windowId);
        });
}
function addTrainButtonsToWindow(window) {
    browser.trainButtons.addButtonsToWindowById(window.id);
    addTrainButtonsListener(window.id);
}
function addTrainButtonsToNormalWindows() {
    browser.windows.getAll().then((windows) => {
        windows.forEach(function (window) {
            // Skip popup, devtools, etc.
            if (window.type !== "normal") return;

            addTrainButtonsToWindow(window);
        });
    });
}

browser.storage.local.get(libBackground.defaultOptions.keys).then((localStorage) => {
    const missing = {};
    for (const k in libBackground.defaultOptions) {
        if (!Object.prototype.hasOwnProperty.call(localStorage, k)) {
            missing[k] = libBackground.defaultOptions[k];
            localStorage[k] = libBackground.defaultOptions[k];
        }
    }
    if (Object.keys(missing).length > 0) browser.storage.local.set(missing);

    libBackground.syncHeaderPrefs(localStorage.header);

    browser.scoreColumn.setLocalStorage({
        "display-column": localStorage["display-column"],
        "display-columnImageOnlyForPositive": localStorage["display-columnImageOnlyForPositive"],
        "header": localStorage.header
    });
    browser.scoreColumn.init();

    disableSymGroupingMenuitem(localStorage["headers-group_symbols"]);
    disableSymOrderMenuitem(localStorage["headers-symbols_order"]);
});

let lastDisplayedMessageId = null;

browser.mailTabs.onSelectedMessagesChanged.addListener((tab, selectedMessages) => {
    if (selectedMessages.messages.length !== 1) return;
    // The same message was reselected (e.g. after column sorting)
    if (selectedMessages.messages[0].id === lastDisplayedMessageId) return;
    // Hide headers until message is loaded
    ["expandedRspamdSpamnessRow", "expandedRspamdSpamnessRulesRow"].forEach(function (id) {
        browser.spamHeaders.setHeaderHidden(tab.id, id, true);
    });
});

browser.messageDisplay.onMessageDisplayed.addListener((tab, message) => {
    lastDisplayedMessageId = message.id;
    browser.messages.getFull(message.id).then(async (messagepart) => {
        const {headers} = messagepart;
        if (headers) await messageHeader.displayHeaders(false, tab, message, headers);
    });
});

browser.runtime.onMessage.addListener(function handleMessage(request, sender, sendResponse) {
    switch (request.method) {
    case "addTrainButtonsToNormalWindows":
        addTrainButtonsToNormalWindows();
        break;
    case "getRulesDialogContent":
        (function () {
            const re = /( +(?:(?:Symbol?: )?[^) ]+\)(?:\[[^\]]*\])?|Message-ID: [^ ]+?))/g;
            const content = messageHeader.headerStr[0].replace(re, "\n$1");
            sendResponse({response: content});
        })();
        break;
    case "syncHeaderPrefs":
        libBackground.syncHeaderPrefs(request.data);
        break;
    default:
        libBackground.error("Unknown request: " + request);
    }
});

async function addControlsToWindow(window) {
    const localStorage =
        await browser.storage.local.get(["trainingButtons-enabled", "headers-symbols_order", "headers-group_symbols"]);
    if (localStorage["trainingButtons-enabled"]) {
        addTrainButtonsToWindow(window);
    }

    browser.popup.addPopupToWindowById(
        window.id,
        localStorage["headers-symbols_order"], localStorage["headers-group_symbols"]
    );
    browser.popup.onSymbolPopupCommand.addListener(function (id) {
        switch (id) {
        case "copyMenuitem":
            break;
        case "rspamdSpamnessSymbolPopupSortByName":
            sortSymbols("name");
            break;
        case "rspamdSpamnessSymbolPopupSortByScore":
            sortSymbols("score");
            break;
        case "rspamdSpamnessSymbolPopupGroup":
            groupSymbols(true);
            break;
        case "rspamdSpamnessSymbolPopupUngroup":
            groupSymbols(false);
            break;
        case "rspamdSpamnessSymbolPopupOpenRulesDialog":
            libBackground.createPopupWindow("/content/rulesDialog.html", 200, 200);
            break;
        case "rspamdSpamnessSymbolPopupOptions":
            browser.runtime.openOptionsPage();
            break;
        default:
            libBackground.error("Unknown menuitem id: " + id);
        }
    });
}

browser.windows.getAll({windowTypes: ["normal"]}).then((windows) => {
    windows.forEach(function (window) {
        addControlsToWindow(window);
    });
});

browser.windows.onCreated.addListener((window) => {
    // Skip popup, devtools, etc.
    if (window.type !== "normal") return;
    addControlsToWindow(window);
});

(function appendPopup() {
    function onCreated() {
        if (browser.runtime.lastError) libBackground.error(browser.runtime.lastError);
    }

    function appendMenuitem(id, label, command) {
        browser.menus.create({
            contexts: ["all"],
            id: id,
            onclick: command,
            title: messenger.i18n.getMessage(label)
        }, onCreated);
    }

    function menuseparator() {
        browser.menus.create({
            contexts: ["all"],
            type: "separator"
        }, onCreated);
    }

    appendMenuitem(
        "rspamdSpamnessSymbolPopupSortByName",
        "spamness.popupSortByName.label",
        () => sortSymbols("name")
    );
    appendMenuitem(
        "rspamdSpamnessSymbolPopupSortByScore",
        "spamness.popupSortByScore.label",
        () => sortSymbols("score")
    );
    menuseparator();
    appendMenuitem(
        "rspamdSpamnessSymbolPopupGroup",
        "spamness.popupGroup.label",
        () => groupSymbols(true)
    );
    appendMenuitem(
        "rspamdSpamnessSymbolPopupUngroup",
        "spamness.popupUngroup.label",
        () => groupSymbols(false)
    );
    menuseparator();
    appendMenuitem(
        "open-rules",
        "spamness.popupRawExtendedHeader.label",
        () => libBackground.createPopupWindow("/content/rulesDialog.html", 200, 200)
    );
    menuseparator();
    appendMenuitem(
        "open-options",
        "spamnessOptions.title",
        () => browser.runtime.openOptionsPage()
    );
}());

browser.spamHeaders.init();
<commit_msg>Use API for windows filtering<commit_after>/* global browser, libBackground, messenger, messageHeader */

"use strict";

function disableSymGroupingMenuitem(group) {
    browser.menus.update("rspamdSpamnessSymbolPopupGroup", {enabled: !group});
    browser.menus.update("rspamdSpamnessSymbolPopupUngroup", {enabled: group});
}
function disableSymOrderMenuitem(order) {
    browser.menus.update("rspamdSpamnessSymbolPopupSortByName", {enabled: (order !== "name")});
    browser.menus.update("rspamdSpamnessSymbolPopupSortByScore", {enabled: (order !== "score")});
}
function groupSymbols(group) {
    disableSymGroupingMenuitem(group);
    browser.popup.disableSymGroupingMenuitem(group);
    browser.storage.local.set({"headers-group_symbols": group});
    messageHeader.updateHeaders();
}
function sortSymbols(order) {
    disableSymOrderMenuitem(order);
    browser.popup.disableSymOrderMenuitem(order);
    browser.storage.local.set({"headers-symbols_order": order});
    messageHeader.updateHeaders();
}

async function moveMessage(buttonId) {
    let ids = [];
    let message = null;
    const tabs = await browser.tabs.query({active: true, currentWindow: true});
    if (tabs[0].mailTab) {
        // The tab is a Thunderbird 3-pane tab
        const messageList = await browser.mailTabs.getSelectedMessages();
        if (!messageList.messages.length) return;
        ids = messageList.messages.map((msg) => msg.id);
        [message] = messageList.messages;
    } else {
        // The tab is not a Thunderbird 3-pane tab, presumable a message pane tab
        message = await browser.messageDisplay.getDisplayedMessage(tabs[0].id);
        ids = [message.id];
    }
    const {accountId} = message.folder;
    const trainFolder = {
        rspamdSpamnessButtonHam: "TrainHam",
        rspamdSpamnessButtonSpam: "TrainSpam"
    };
    const {account, path} = await libBackground.getDestination(accountId, trainFolder[buttonId]);
    if (!account) {
        libBackground.displayNotification("spamness.alertText.destinationFolderAccountIsNotSet");
        return;
    }
    if (!path) {
        libBackground.displayNotification("spamness.alertText.destinationFolderLocationIsNotSet");
        return;
    }
    const folders = path.trim().replace(/(^\/|\/$)/g, "").split("/");
    const destination = folders.reduce(function (prev, curr, i, arr) {
        const folder = (i) ? prev.subFolders : prev.folders;
        const subFolder =
            folder.find((f) => f.path.replace(/.*[/]/, "") === decodeURIComponent(curr) || f.name === curr);
        // Break loop by mutating iterated copy
        if (!subFolder) arr.splice(1);
        return subFolder;
    }, account);

    if (typeof destination === "undefined") {
        libBackground.displayNotification("spamness.alertText.destinationFolderNotFound");
        return;
    }

    browser.storage.local.get(["trainingButtonHam-defaultAction", "trainingButtonSpam-defaultAction"])
        .then((localStorage) => {
            const action = (buttonId === "rspamdSpamnessButtonHam" &&
                    (localStorage["trainingButtonHam-defaultAction"] === "copy") ||
                buttonId === "rspamdSpamnessButtonSpam" &&
                    (localStorage["trainingButtonSpam-defaultAction"] === "copy"))
                ? "copy"
                : "move";
            browser.messages[action](ids, destination).catch(function (error) {
                libBackground.error(error);
                if ((/^Unexpected error (copy|mov)ing messages: 2147500037$/).test(error.message)) {
                    libBackground.displayNotification(
                        "spamness.alertText.error_2147500037_workaround",
                        error.message + "\n\n"
                    );
                }
            });
        });
}
function addTrainButtonsListener(windowId) {
    ["rspamdSpamnessButtonHam", "rspamdSpamnessButtonSpam"]
        .forEach(function (targetId) {
            browser.trainButtons.onButtonCommand.addListener((buttonId) => {
                moveMessage(buttonId);
            }, targetId, windowId);
        });
}
function addTrainButtonsToWindow(window) {
    browser.trainButtons.addButtonsToWindowById(window.id);
    addTrainButtonsListener(window.id);
}
function addTrainButtonsToNormalWindows() {
    browser.windows.getAll({windowTypes: ["normal"]}).then((windows) => {
        windows.forEach(function (window) {
            addTrainButtonsToWindow(window);
        });
    });
}

browser.storage.local.get(libBackground.defaultOptions.keys).then((localStorage) => {
    const missing = {};
    for (const k in libBackground.defaultOptions) {
        if (!Object.prototype.hasOwnProperty.call(localStorage, k)) {
            missing[k] = libBackground.defaultOptions[k];
            localStorage[k] = libBackground.defaultOptions[k];
        }
    }
    if (Object.keys(missing).length > 0) browser.storage.local.set(missing);

    libBackground.syncHeaderPrefs(localStorage.header);

    browser.scoreColumn.setLocalStorage({
        "display-column": localStorage["display-column"],
        "display-columnImageOnlyForPositive": localStorage["display-columnImageOnlyForPositive"],
        "header": localStorage.header
    });
    browser.scoreColumn.init();

    disableSymGroupingMenuitem(localStorage["headers-group_symbols"]);
    disableSymOrderMenuitem(localStorage["headers-symbols_order"]);
});

let lastDisplayedMessageId = null;

browser.mailTabs.onSelectedMessagesChanged.addListener((tab, selectedMessages) => {
    if (selectedMessages.messages.length !== 1) return;
    // The same message was reselected (e.g. after column sorting)
    if (selectedMessages.messages[0].id === lastDisplayedMessageId) return;
    // Hide headers until message is loaded
    ["expandedRspamdSpamnessRow", "expandedRspamdSpamnessRulesRow"].forEach(function (id) {
        browser.spamHeaders.setHeaderHidden(tab.id, id, true);
    });
});

browser.messageDisplay.onMessageDisplayed.addListener((tab, message) => {
    lastDisplayedMessageId = message.id;
    browser.messages.getFull(message.id).then(async (messagepart) => {
        const {headers} = messagepart;
        if (headers) await messageHeader.displayHeaders(false, tab, message, headers);
    });
});

browser.runtime.onMessage.addListener(function handleMessage(request, sender, sendResponse) {
    switch (request.method) {
    case "addTrainButtonsToNormalWindows":
        addTrainButtonsToNormalWindows();
        break;
    case "getRulesDialogContent":
        (function () {
            const re = /( +(?:(?:Symbol?: )?[^) ]+\)(?:\[[^\]]*\])?|Message-ID: [^ ]+?))/g;
            const content = messageHeader.headerStr[0].replace(re, "\n$1");
            sendResponse({response: content});
        })();
        break;
    case "syncHeaderPrefs":
        libBackground.syncHeaderPrefs(request.data);
        break;
    default:
        libBackground.error("Unknown request: " + request);
    }
});

async function addControlsToWindow(window) {
    const localStorage =
        await browser.storage.local.get(["trainingButtons-enabled", "headers-symbols_order", "headers-group_symbols"]);
    if (localStorage["trainingButtons-enabled"]) {
        addTrainButtonsToWindow(window);
    }

    browser.popup.addPopupToWindowById(
        window.id,
        localStorage["headers-symbols_order"], localStorage["headers-group_symbols"]
    );
    browser.popup.onSymbolPopupCommand.addListener(function (id) {
        switch (id) {
        case "copyMenuitem":
            break;
        case "rspamdSpamnessSymbolPopupSortByName":
            sortSymbols("name");
            break;
        case "rspamdSpamnessSymbolPopupSortByScore":
            sortSymbols("score");
            break;
        case "rspamdSpamnessSymbolPopupGroup":
            groupSymbols(true);
            break;
        case "rspamdSpamnessSymbolPopupUngroup":
            groupSymbols(false);
            break;
        case "rspamdSpamnessSymbolPopupOpenRulesDialog":
            libBackground.createPopupWindow("/content/rulesDialog.html", 200, 200);
            break;
        case "rspamdSpamnessSymbolPopupOptions":
            browser.runtime.openOptionsPage();
            break;
        default:
            libBackground.error("Unknown menuitem id: " + id);
        }
    });
}

browser.windows.getAll({windowTypes: ["normal"]}).then((windows) => {
    windows.forEach(function (window) {
        addControlsToWindow(window);
    });
});

browser.windows.onCreated.addListener((window) => {
    // Skip popup, devtools, etc.
    if (window.type !== "normal") return;
    addControlsToWindow(window);
});

(function appendPopup() {
    function onCreated() {
        if (browser.runtime.lastError) libBackground.error(browser.runtime.lastError);
    }

    function appendMenuitem(id, label, command) {
        browser.menus.create({
            contexts: ["all"],
            id: id,
            onclick: command,
            title: messenger.i18n.getMessage(label)
        }, onCreated);
    }

    function menuseparator() {
        browser.menus.create({
            contexts: ["all"],
            type: "separator"
        }, onCreated);
    }

    appendMenuitem(
        "rspamdSpamnessSymbolPopupSortByName",
        "spamness.popupSortByName.label",
        () => sortSymbols("name")
    );
    appendMenuitem(
        "rspamdSpamnessSymbolPopupSortByScore",
        "spamness.popupSortByScore.label",
        () => sortSymbols("score")
    );
    menuseparator();
    appendMenuitem(
        "rspamdSpamnessSymbolPopupGroup",
        "spamness.popupGroup.label",
        () => groupSymbols(true)
    );
    appendMenuitem(
        "rspamdSpamnessSymbolPopupUngroup",
        "spamness.popupUngroup.label",
        () => groupSymbols(false)
    );
    menuseparator();
    appendMenuitem(
        "open-rules",
        "spamness.popupRawExtendedHeader.label",
        () => libBackground.createPopupWindow("/content/rulesDialog.html", 200, 200)
    );
    menuseparator();
    appendMenuitem(
        "open-options",
        "spamnessOptions.title",
        () => browser.runtime.openOptionsPage()
    );
}());

browser.spamHeaders.init();
<|endoftext|>


<commit_before>'use strict';

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

/**
 *
 * A state maintainer for a smooth transition between
 *    numerically-specified states.  Example numeric states include floats or
 *    Transfornm objects.
 *
 *    An initial state is set with the constructor or set(startValue). A
 *    corresponding end state and transition are set with set(endValue,
 *    transition). Subsequent calls to set(endValue, transition) begin at
 *    the last state. Calls to get(timestamp) provide the _interpolated state
 *    along the way.
 *
 *   Note that there is no event loop here - calls to get() are the only way
 *    to find out state projected to the current (or provided) time and are
 *    the only way to trigger callbacks. Usually this kind of object would
 *    be part of the render() path of a visible component.
 *
 * @class TweenTransition
 * @constructor
 *
 * @param {Object} options TODO
 *    beginning state
 */
function TweenTransition(options) {
    this.options = Object.create(TweenTransition.DEFAULT_OPTIONS);
    if (options) this.setOptions(options);

    this._startTime = 0;
    this._startValue = 0;
    this._updateTime = 0;
    this._endValue = 0;
    this._curve = undefined;
    this._duration = 0;
    this._active = false;
    this._callback = undefined;
    this.state = 0;
    this.velocity = undefined;
    this._paused = 0;
}

TweenTransition.SUPPORTS_MULTIPLE = true;
TweenTransition.DEFAULT_OPTIONS = {
    curve: 'linear',
    duration: 500,
    speed: 0 /* considered only if positive */
};

 // Interpolate: If a linear function f(0) = a, f(1) = b, then return f(t)
function _interpolate(a, b, t) {
    return ((1 - t) * a) + (t * b);
}

function _clone(obj) {
    if (obj instanceof Object) {
        if (obj instanceof Array) return obj.slice(0);
        else return Object.create(obj);
    }
    else return obj;
}

// Fill in missing properties in "transition" with those in defaultTransition, and
//   convert internal named curve to function object, returning as new
//   object.
function _normalize(transition, defaultTransition) {
    var result = {curve: defaultTransition.curve};
    if (defaultTransition.duration) result.duration = defaultTransition.duration;
    if (defaultTransition.speed) result.speed = defaultTransition.speed;
    if (transition instanceof Object) {
        if (transition.duration !== undefined) result.duration = transition.duration;
        if (transition.curve) result.curve = transition.curve;
        if (transition.speed) result.speed = transition.speed;
    }
    if (typeof result.curve === 'string') result.curve = Easing.getCurve(result.curve);
    return result;
}

/**
 * Set internal options, overriding any default options.
 *
 * @method setOptions
 *
 *
 * @param {Object} options options object
 * @param {Object} [options.curve] function mapping [0,1] to [0,1] or identifier
 * @param {Number} [options.duration] duration in ms
 * @param {Number} [options.speed] speed in pixels per ms
 */
TweenTransition.prototype.setOptions = function setOptions(options) {
    if (options.curve !== undefined) this.options.curve = options.curve;
    if (options.duration !== undefined) this.options.duration = options.duration;
    if (options.speed !== undefined) this.options.speed = options.speed;
};

/**
 * Add transition to end state to the queue of pending transitions. Special
 *    Use: calling without a transition resets the object to that state with
 *    no pending actions
 *
 * @method set
 *
 *
 * @param {number|FamousMatrix|Array.Number|Object.<number, number>} endValue
 *    end state to which we _interpolate
 * @param {transition=} transition object of type {duration: number, curve:
 *    f[0,1] -> [0,1] or name}. If transition is omitted, change will be
 *    instantaneous.
 * @param {function()=} callback Zero-argument function to call on observed
 *    completion (t=1)
 */
TweenTransition.prototype.set = function set(endValue, transition, callback) {
    if (!transition) {
        this.reset(endValue);
        if (callback) callback();
        return;
    }

    this._startValue = _clone(this.get());
    transition = _normalize(transition, this.options);
    if (transition.speed) {
        var startValue = this._startValue;
        if (startValue instanceof Object) {
            var variance = 0;
            for (var i in startValue) variance += (endValue[i] - startValue[i]) * (endValue[i] - startValue[i]);
            transition.duration = Math.sqrt(variance) / transition.speed;
        }
        else {
            transition.duration = Math.abs(endValue - startValue) / transition.speed;
        }
    }

    this._startTime = Date.now();
    this._endValue = _clone(endValue);
    this._startVelocity = _clone(transition.velocity);
    this._duration = transition.duration;
    this._curve = transition.curve;
    this._active = true;
    this._callback = callback;
};

/**
 * Cancel all transitions and reset to a stable state
 *
 * @method reset
 *
 * @param {number|Array.Number|Object.<number, number>} startValue
 *    starting state
 * @param {number} startVelocity
 *    starting velocity
 */
TweenTransition.prototype.reset = function reset(startValue, startVelocity) {
    if (this._callback) {
        var callback = this._callback;
        this._callback = undefined;
        callback();
    }
    this.state = _clone(startValue);
    this.velocity = _clone(startVelocity);
    this._startTime = 0;
    this._duration = 0;
    this._updateTime = 0;
    this._startValue = this.state;
    this._startVelocity = this.velocity;
    this._endValue = this.state;
    this._active = false;
};

/**
 * Get current velocity
 *
 * @method getVelocity
 *
 * @returns {Number} velocity
 */
TweenTransition.prototype.getVelocity = function getVelocity() {
    return this.velocity;
};

/**
 * Get interpolated state of current action at provided time. If the last
 *    action has completed, invoke its callback.
 *
 * @method get
 *
 *
 * @param {number=} timestamp Evaluate the curve at a normalized version of this
 *    time. If omitted, use current time. (Unix epoch time)
 * @return {number|Object.<number|string, number>} beginning state
 *    _interpolated to this point in time.
 */
TweenTransition.prototype.get = function get(timestamp) {
    if (this._paused) timestamp = this._paused;
    this.update(timestamp);
    return this.state;
};

function _calculateVelocity(current, start, curve, duration, t) {
    var velocity;
    var eps = 1e-7;
    var speed = (curve(t) - curve(t - eps)) / eps;
    if (current instanceof Array) {
        velocity = [];
        for (var i = 0; i < current.length; i++){
            if (typeof current[i] === 'number')
                velocity[i] = speed * (current[i] - start[i]) / duration;
            else
                velocity[i] = 0;
        }

    }
    else velocity = speed * (current - start) / duration;
    return velocity;
}

function _calculateState(start, end, t) {
    var state;
    if (start instanceof Array) {
        state = [];
        for (var i = 0; i < start.length; i++) {
            if (typeof start[i] === 'number')
                state[i] = _interpolate(start[i], end[i], t);
            else
                state[i] = start[i];
        }
    }
    else state = _interpolate(start, end, t);
    return state;
}

/**
 * Update internal state to the provided timestamp. This may invoke the last
 *    callback and begin a new action.
 *
 * @method update
 *
 *
 * @param {number=} timestamp Evaluate the curve at a normalized version of this
 *    time. If omitted, use current time. (Unix epoch time)
 */
TweenTransition.prototype.update = function update(timestamp) {
    if (!this._active) {
        if (this._callback) {
            var callback = this._callback;
            this._callback = undefined;
            callback();
        }
        return;
    }

    if (!timestamp) timestamp = Date.now();
    if (this._updateTime >= timestamp) return;
    this._updateTime = timestamp;

    var timeSinceStart = timestamp - this._startTime;
    if (timeSinceStart >= this._duration) {
        this.state = this._endValue;
        this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, 1);
        this._active = false;
    }
    else if (timeSinceStart < 0) {
        this.state = this._startValue;
        this.velocity = this._startVelocity;
    }
    else {
        var t = timeSinceStart / this._duration;
        this.state = _calculateState(this._startValue, this._endValue, this._curve(t));
        this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, t);
    }
};

/**
 * Is there at least one action pending completion?
 *
 * @method isActive
 *
 *
 * @return {boolean}
 */
TweenTransition.prototype.isActive = function isActive() {
    return this._active;
};

/**
 * Halt transition at current state and erase all pending actions.
 *
 * @method halt
 *
 */
TweenTransition.prototype.halt = function halt() {
    this.reset(this.get());
};

/**
 * Pause transition.
 *
 * @method pause
 * @return {TweenTransition} this
 */
TweenTransition.prototype.pause = function pause() {
    this._paused = Date.now();
    return this;
};

/**
 * Check if transition has been paused.
 *
 * @method isPaused
 *
 * @return {Boolean} if current transition is paused
 */
TweenTransition.prototype.isPaused = function isPaused() {
    return !!this._paused;
};

/**
 * Resume transition.
 *
 * @method resume
 * @return {TweenTransition} this
 */
TweenTransition.prototype.resume = function resume() {
    var progressBeforePause = this._paused - this._startTime;
    this._startTime = Date.now() - progressBeforePause;
    this._paused = 0;
    return this;
};

module.exports = TweenTransition;
<commit_msg>fix: Enable user to override now function<commit_after>'use strict';

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

/**
 *
 * A state maintainer for a smooth transition between
 *    numerically-specified states.  Example numeric states include floats or
 *    Transfornm objects.
 *
 *    An initial state is set with the constructor or set(startValue). A
 *    corresponding end state and transition are set with set(endValue,
 *    transition). Subsequent calls to set(endValue, transition) begin at
 *    the last state. Calls to get(timestamp) provide the _interpolated state
 *    along the way.
 *
 *   Note that there is no event loop here - calls to get() are the only way
 *    to find out state projected to the current (or provided) time and are
 *    the only way to trigger callbacks. Usually this kind of object would
 *    be part of the render() path of a visible component.
 *
 * @class TweenTransition
 * @constructor
 *
 * @param {Object} options TODO
 *    beginning state
 */
function TweenTransition(options) {
    this.options = Object.create(TweenTransition.DEFAULT_OPTIONS);
    if (options) this.setOptions(options);

    this._startTime = 0;
    this._startValue = 0;
    this._updateTime = 0;
    this._endValue = 0;
    this._curve = undefined;
    this._duration = 0;
    this._active = false;
    this._callback = undefined;
    this.state = 0;
    this.velocity = undefined;
    this._paused = 0;
}

TweenTransition.now = function() {
    return Date.now();
};

TweenTransition.SUPPORTS_MULTIPLE = true;
TweenTransition.DEFAULT_OPTIONS = {
    curve: 'linear',
    duration: 500,
    speed: 0 /* considered only if positive */
};

 // Interpolate: If a linear function f(0) = a, f(1) = b, then return f(t)
function _interpolate(a, b, t) {
    return ((1 - t) * a) + (t * b);
}

function _clone(obj) {
    if (obj instanceof Object) {
        if (obj instanceof Array) return obj.slice(0);
        else return Object.create(obj);
    }
    else return obj;
}

// Fill in missing properties in "transition" with those in defaultTransition, and
//   convert internal named curve to function object, returning as new
//   object.
function _normalize(transition, defaultTransition) {
    var result = {curve: defaultTransition.curve};
    if (defaultTransition.duration) result.duration = defaultTransition.duration;
    if (defaultTransition.speed) result.speed = defaultTransition.speed;
    if (transition instanceof Object) {
        if (transition.duration !== undefined) result.duration = transition.duration;
        if (transition.curve) result.curve = transition.curve;
        if (transition.speed) result.speed = transition.speed;
    }
    if (typeof result.curve === 'string') result.curve = Easing.getCurve(result.curve);
    return result;
}

/**
 * Set internal options, overriding any default options.
 *
 * @method setOptions
 *
 *
 * @param {Object} options options object
 * @param {Object} [options.curve] function mapping [0,1] to [0,1] or identifier
 * @param {Number} [options.duration] duration in ms
 * @param {Number} [options.speed] speed in pixels per ms
 */
TweenTransition.prototype.setOptions = function setOptions(options) {
    if (options.curve !== undefined) this.options.curve = options.curve;
    if (options.duration !== undefined) this.options.duration = options.duration;
    if (options.speed !== undefined) this.options.speed = options.speed;
};

/**
 * Add transition to end state to the queue of pending transitions. Special
 *    Use: calling without a transition resets the object to that state with
 *    no pending actions
 *
 * @method set
 *
 *
 * @param {number|FamousMatrix|Array.Number|Object.<number, number>} endValue
 *    end state to which we _interpolate
 * @param {transition=} transition object of type {duration: number, curve:
 *    f[0,1] -> [0,1] or name}. If transition is omitted, change will be
 *    instantaneous.
 * @param {function()=} callback Zero-argument function to call on observed
 *    completion (t=1)
 */
TweenTransition.prototype.set = function set(endValue, transition, callback) {
    if (!transition) {
        this.reset(endValue);
        if (callback) callback();
        return;
    }

    this._startValue = _clone(this.get());
    transition = _normalize(transition, this.options);
    if (transition.speed) {
        var startValue = this._startValue;
        if (startValue instanceof Object) {
            var variance = 0;
            for (var i in startValue) variance += (endValue[i] - startValue[i]) * (endValue[i] - startValue[i]);
            transition.duration = Math.sqrt(variance) / transition.speed;
        }
        else {
            transition.duration = Math.abs(endValue - startValue) / transition.speed;
        }
    }

    this._startTime = TweenTransition.now();
    this._endValue = _clone(endValue);
    this._startVelocity = _clone(transition.velocity);
    this._duration = transition.duration;
    this._curve = transition.curve;
    this._active = true;
    this._callback = callback;
};

/**
 * Cancel all transitions and reset to a stable state
 *
 * @method reset
 *
 * @param {number|Array.Number|Object.<number, number>} startValue
 *    starting state
 * @param {number} startVelocity
 *    starting velocity
 */
TweenTransition.prototype.reset = function reset(startValue, startVelocity) {
    if (this._callback) {
        var callback = this._callback;
        this._callback = undefined;
        callback();
    }
    this.state = _clone(startValue);
    this.velocity = _clone(startVelocity);
    this._startTime = 0;
    this._duration = 0;
    this._updateTime = 0;
    this._startValue = this.state;
    this._startVelocity = this.velocity;
    this._endValue = this.state;
    this._active = false;
};

/**
 * Get current velocity
 *
 * @method getVelocity
 *
 * @returns {Number} velocity
 */
TweenTransition.prototype.getVelocity = function getVelocity() {
    return this.velocity;
};

/**
 * Get interpolated state of current action at provided time. If the last
 *    action has completed, invoke its callback.
 *
 * @method get
 *
 *
 * @param {number=} timestamp Evaluate the curve at a normalized version of this
 *    time. If omitted, use current time. (Unix epoch time)
 * @return {number|Object.<number|string, number>} beginning state
 *    _interpolated to this point in time.
 */
TweenTransition.prototype.get = function get(timestamp) {
    if (this._paused) timestamp = this._paused;
    this.update(timestamp);
    return this.state;
};

function _calculateVelocity(current, start, curve, duration, t) {
    var velocity;
    var eps = 1e-7;
    var speed = (curve(t) - curve(t - eps)) / eps;
    if (current instanceof Array) {
        velocity = [];
        for (var i = 0; i < current.length; i++){
            if (typeof current[i] === 'number')
                velocity[i] = speed * (current[i] - start[i]) / duration;
            else
                velocity[i] = 0;
        }

    }
    else velocity = speed * (current - start) / duration;
    return velocity;
}

function _calculateState(start, end, t) {
    var state;
    if (start instanceof Array) {
        state = [];
        for (var i = 0; i < start.length; i++) {
            if (typeof start[i] === 'number')
                state[i] = _interpolate(start[i], end[i], t);
            else
                state[i] = start[i];
        }
    }
    else state = _interpolate(start, end, t);
    return state;
}

/**
 * Update internal state to the provided timestamp. This may invoke the last
 *    callback and begin a new action.
 *
 * @method update
 *
 *
 * @param {number=} timestamp Evaluate the curve at a normalized version of this
 *    time. If omitted, use current time. (Unix epoch time)
 */
TweenTransition.prototype.update = function update(timestamp) {
    if (!this._active) {
        if (this._callback) {
            var callback = this._callback;
            this._callback = undefined;
            callback();
        }
        return;
    }

    if (!timestamp) timestamp = TweenTransition.now();
    if (this._updateTime >= timestamp) return;
    this._updateTime = timestamp;

    var timeSinceStart = timestamp - this._startTime;
    if (timeSinceStart >= this._duration) {
        this.state = this._endValue;
        this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, 1);
        this._active = false;
    }
    else if (timeSinceStart < 0) {
        this.state = this._startValue;
        this.velocity = this._startVelocity;
    }
    else {
        var t = timeSinceStart / this._duration;
        this.state = _calculateState(this._startValue, this._endValue, this._curve(t));
        this.velocity = _calculateVelocity(this.state, this._startValue, this._curve, this._duration, t);
    }
};

/**
 * Is there at least one action pending completion?
 *
 * @method isActive
 *
 *
 * @return {boolean}
 */
TweenTransition.prototype.isActive = function isActive() {
    return this._active;
};

/**
 * Halt transition at current state and erase all pending actions.
 *
 * @method halt
 *
 */
TweenTransition.prototype.halt = function halt() {
    this.reset(this.get());
};

/**
 * Pause transition.
 *
 * @method pause
 * @return {TweenTransition} this
 */
TweenTransition.prototype.pause = function pause() {
    this._paused = TweenTransition.now();
    return this;
};

/**
 * Check if transition has been paused.
 *
 * @method isPaused
 *
 * @return {Boolean} if current transition is paused
 */
TweenTransition.prototype.isPaused = function isPaused() {
    return !!this._paused;
};

/**
 * Resume transition.
 *
 * @method resume
 * @return {TweenTransition} this
 */
TweenTransition.prototype.resume = function resume() {
    var progressBeforePause = this._paused - this._startTime;
    this._startTime = TweenTransition.now() - progressBeforePause;
    this._paused = 0;
    return this;
};

module.exports = TweenTransition;
<|endoftext|>


<commit_before>/**
 * Infinity Systems component WebSocketClient
 *
 * @author Igor Sapegin aka Rendol <sapegin.in@gmail.com>
 */
IS.reg('components.WebSocketClient', function () {
	return $.extend({}, {
		// WebSocket Client options
		options: {},

		// Private properties
		_sendQueue: [],
		_sendCallbacks: [],
		_sendProcessStarted: false,

		constructor: function (data) {
			var me = $.extend(this, data);
			// Веб-Сокет
			me.connect = new this._client($.extend({
				onMessage: function (data) {
					for (var category in data) {
						if (category != 'callback' && category != 'Exception') {
							for (var name in data[category]) {
								me.trigger(category + '.' + name, data[category][name]);
							}
						}
					}

					if ('callback' in data) {
						me._runCallback(data.callback);
					}

					if ('Exception' in data) {
						console.log(data.Exception);
						throw new Error(data.Exception.message);
					}

				}
			}, me.options));
			return this;
		},

		msg: function (data, callback) {
			this._sendQueue.push({
				data: data,
				callback: callback
			});
			this._run();
		},

		_run: function () {
			var me = this;
			if (!me._sendProcessStarted) {
				me._sendProcessStarted = true;
				me._iterationCallback();
			}
		},
		_runCallback: function (index) {
			this._sendCallbacks[index]();
		},
		_iterationCallback: function () {
			var me = this;
			var task = me._sendQueue.shift();
			if (task) {
				task.data[3] = me._sendCallbacks.length;
				me._sendCallbacks.push(function () {
					if (task.callback) {
						task.callback();
					}
					me._iterationCallback()
				});
				me.connect.send(JSON.stringify(task.data));
			}
			else {
				me._sendProcessStarted = false;
			}
		},

		/**
		 * Event model
		 */
		_handlers: {},
		trigger: function (name, args) {
			var handlers = this._handlers;
			if (name in handlers) {
				for (var i = 0, ln = handlers[name].length; i < ln; i++) {
					handlers[name][i](args);
				}
			}
			else {
				console.log('Not found triggers for:', name);
			}
		},
		on: function selfCalle() {
			var me = this,
				args = arguments,
				i,
				ln,
				name,
				handlers;

			if (args[0] instanceof Array) {
				handlers = args[0];
				for (i = 0, ln = handlers.length; i < ln; i++) {
					selfCalle.apply(me, [handlers[i]]);
				}
			}
			else if (args[0] instanceof Object) {
				handlers = args[0];
				for (name in handlers) {
					selfCalle.apply(me, [name, handlers[name]]);
				}
			}
			else {
				name = args[0];
				handlers = args[1];
				if (handlers instanceof Array) {
					for (i = 0, ln = handlers.length; i < ln; i++) {
						selfCalle.apply(me, [name, handlers[i]]);
					}
				}
				else {
					if (!(name in me._handlers)) {
						me._handlers[name] = [];
					}
					me._handlers[name].push(handlers);
				}
			}
			return this;
		},
		onDebounce: function (name, handler, wait, unique) {
			var me = this;
			if (!(name in me._handlers)) {
				me._handlers[name] = [];
			}
			me._handlers[name].push(
				me.debounce(
					handler,
					wait,
					unique
				)
			);
			return me;
		},
		debounce: function (func, wait, unique) {
			var timeouts = {};
			return function () {
				var context = this, args = arguments;
				var uniqueId = unique.apply(context, args);

				clearTimeout(timeouts[uniqueId]);
				timeouts[uniqueId] = setTimeout(function () {
					timeouts[uniqueId] = null;
					func.apply(context, args);
				}, wait);
			};
		},
		_client: function (opts) {
			var o = $.extend(
				{
					protocol: 'ws',
					host: 'localhost',
					port: 80,
					registerId: null,
					sessionId: null,
					onOpen: function () {
						console.log('default handler for event @onOpen');
					},
					onClose: function (data) {
						console.log('default handler for event @onClose', data);
					},
					onMessage: function (data) {
						console.log('default handler for event @onMessage', data);
					}
				}, opts
			);
			var socket = new WebSocket(o.protocol + '://' + o.host + ':' + o.port + '/');
			socket.onopen = function (msg) {
				o.onOpen();
			};
			socket.onmessage = function (msg) {
				if (msg.data) {
					var data = JSON.parse(msg.data + "");
					if (data[1]) {
						o.onMessage(data[1]);
					}
				}
			};
			socket.onclose = function (msg) {
				o.onClose(msg);
			};
			return socket;
		}
	});
})
;<commit_msg>Update callback params<commit_after>/**
 * Infinity Systems component WebSocketClient
 *
 * @author Igor Sapegin aka Rendol <sapegin.in@gmail.com>
 */
IS.reg('components.WebSocketClient', function () {
    return $.extend({}, {
        // WebSocket Client options
        options: {},

        // Private properties
        _sendQueue: [],
        _sendCallbacks: [],
        _sendProcessStarted: false,

        constructor: function (data) {
            var me = $.extend(this, data);
            // Веб-Сокет
            me.connect = new this._client($.extend({
                onMessage: function (data) {

                    for (var category in data) {
                        if (category != 'callback' && category != 'Exception') {
                            for (var name in data[category]) {
                                me.trigger(category + '.' + name, data[category][name]);
                            }
                        }
                    }

                    if ('callback' in data) {
                        me._runCallback(data.callback.index, data.callback.data);
                    }

                    if ('Exception' in data) {
                        console.log(data.Exception);
                        throw new Error(data.Exception.message);
                    }

                }
            }, me.options));
            return this;
        },

        msg: function (data, callback) {
            this._sendQueue.push({
                data: data,
                callback: callback
            });
            this._run();
        },

        _run: function () {
            var me = this;
            if (!me._sendProcessStarted) {
                me._sendProcessStarted = true;
                me._iterationCallback();
            }
        },
        _runCallback: function (index, data) {
            this._sendCallbacks[index](data);
        },
        _iterationCallback: function () {
            var me = this;
            var task = me._sendQueue.shift();
            if (task) {
                task.data['callback'] = me._sendCallbacks.length;
                me._sendCallbacks.push(function () {
                    if (task.callback) {
                        task.callback();
                    }
                    me._iterationCallback()
                });
                me.connect.send(JSON.stringify(task.data));
            }
            else {
                me._sendProcessStarted = false;
            }
        },

        /**
         * Event model
         */
        _handlers: {},
        trigger: function (name, args) {
            var handlers = this._handlers;
            if (name in handlers) {
                for (var i = 0, ln = handlers[name].length; i < ln; i++) {
                    handlers[name][i](args);
                }
            }
            else {
                console.log('Not found triggers for:', name);
            }
        },
        on: function selfCalle() {
            var me = this,
                args = arguments,
                i,
                ln,
                name,
                handlers;

            if (args[0] instanceof Array) {
                handlers = args[0];
                for (i = 0, ln = handlers.length; i < ln; i++) {
                    selfCalle.apply(me, [handlers[i]]);
                }
            }
            else if (args[0] instanceof Object) {
                handlers = args[0];
                for (name in handlers) {
                    selfCalle.apply(me, [name, handlers[name]]);
                }
            }
            else {
                name = args[0];
                handlers = args[1];
                if (handlers instanceof Array) {
                    for (i = 0, ln = handlers.length; i < ln; i++) {
                        selfCalle.apply(me, [name, handlers[i]]);
                    }
                }
                else {
                    if (!(name in me._handlers)) {
                        me._handlers[name] = [];
                    }
                    me._handlers[name].push(handlers);
                }
            }
            return this;
        },
        onDebounce: function (name, handler, wait, unique) {
            var me = this;
            if (!(name in me._handlers)) {
                me._handlers[name] = [];
            }
            me._handlers[name].push(
                me.debounce(
                    handler,
                    wait,
                    unique
                )
            );
            return me;
        },
        debounce: function (func, wait, unique) {
            var timeouts = {};
            return function () {
                var context = this, args = arguments;
                var uniqueId = unique.apply(context, args);

                clearTimeout(timeouts[uniqueId]);
                timeouts[uniqueId] = setTimeout(function () {
                    timeouts[uniqueId] = null;
                    func.apply(context, args);
                }, wait);
            };
        },
        _client: function (opts) {
            var o = $.extend(
                {
                    protocol: 'ws',
                    host: 'localhost',
                    port: 80,
                    registerId: null,
                    sessionId: null,
                    onOpen: function () {
                        console.log('default handler for event @onOpen');
                    },
                    onClose: function (data) {
                        console.log('default handler for event @onClose', data);
                    },
                    onMessage: function (data) {
                        console.log('default handler for event @onMessage', data);
                    }
                }, opts
            );
            var socket = new WebSocket(o.protocol + '://' + o.host + ':' + o.port + '/');
            socket.onopen = function (msg) {
                o.onOpen();
            };
            socket.onmessage = function (msg) {
                if (msg.data) {
                    var data = JSON.parse(msg.data + "");
                    o.onMessage(data);
                }
            };
            socket.onclose = function (msg) {
                o.onClose(msg);
            };
            return socket;
        }
    });
})
;<|endoftext|>


<commit_before>/*
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
Listens for incoming postMessage requests from embedded widgets. The following API is exposed:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID,
    data: {}
    // additional request fields
}

The complete request object is returned to the caller with an additional "response" key like so:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID,
    data: {},
    // additional request fields
    response: { ... }
}

The "api" field is required to use this API, and must be set to "widget" in all requests.

The "action" determines the format of the request and response. All actions can return an error response.

Additional data can be sent as additional, abritrary fields. However, typically the data object should be used.

A success response is an object with zero or more keys.

An error response is a "response" object which consists of a sole "error" key to indicate an error.
They look like:
{
    error: {
        message: "Unable to invite user into room.",
        _error: <Original Error Object>
    }
}
The "message" key should be a human-friendly string.

ACTIONS
=======
** All actions must include an "api" field with valie "widget".**
All actions can return an error response instead of the response outlined below.

content_loaded
--------------
Indicates that widget contet has fully loaded

Request:
 - widgetId is the unique ID of the widget instance in riot / matrix state.
 - No additional fields.
Response:
{
    success: true
}
Example:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID
}

api_version
-----------
Get the current version of the widget postMessage API

Request:
 - No additional fields.
Response:
{
    api_version: "0.0.1"
}
Example:
{
    api: "widget",
    action: "api_version",
}

supported_api_versions
----------------------
Get versions of the widget postMessage API that are currently supported

Request:
 - No additional fields.
Response:
{
    api: "widget"
    supported_versions: ["0.0.1"]
}
Example:
{
    api: "widget",
    action: "supported_api_versions",
}

*/

import URL from 'url';

const WIDGET_API_VERSION = '0.0.1'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [
    '0.0.1',
];

import dis from './dispatcher';

if (!global.mxWidgetMessagingListenerCount) {
    global.mxWidgetMessagingListenerCount = 0;
}
if (!global.mxWidgetMessagingMessageEndpoints) {
    global.mxWidgetMessagingMessageEndpoints = [];
}

/**
 * Register widget message event listeners
 */
function startListening() {
    if (global.mxWidgetMessagingListenerCount === 0) {
        window.addEventListener("message", onMessage, false);
    }
    global.mxWidgetMessagingListenerCount += 1;
}

/**
 * De-register widget message event listeners
 */
function stopListening() {
    global.mxWidgetMessagingListenerCount -= 1;
    if (global.mxWidgetMessagingListenerCount === 0) {
        window.removeEventListener("message", onMessage);
    }
    if (global.mxWidgetMessagingListenerCount < 0) {
        // Make an error so we get a stack trace
        const e = new Error(
            "WidgetMessaging: mismatched startListening / stopListening detected." +
            " Negative count",
        );
        console.error(e);
    }
}

/**
 * Register a widget endpoint for trusted postMessage communication
 * @param {string} widgetId    Unique widget identifier
 * @param {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
 */
function addEndpoint(widgetId, endpointUrl) {
    const u = URL.parse(endpointUrl);
    if (!u || !u.protocol || !u.host) {
        console.warn("Invalid origin");
        return;
    }

    const origin = u.protocol + '//' + u.host;
    const endpoint = new WidgetMessageEndpoint(widgetId, origin);
    if (global.mxWidgetMessagingMessageEndpoints) {
        if (global.mxWidgetMessagingMessageEndpoints.some(function(ep) {
            return (ep.widgetId === widgetId && ep.endpointUrl === endpointUrl);
        })) {
            // Message endpoint already registered
            console.warn("Endpoint already registered");
            return;
        }
        global.mxWidgetMessagingMessageEndpoints.push(endpoint);
    }
}

/**
 * De-register a widget endpoint from trusted communication sources
 * @param  {string} widgetId Unique widget identifier
 * @param  {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
 * @return {boolean} True if endpoint was successfully removed
 */
function removeEndpoint(widgetId, endpointUrl) {
    const u = URL.parse(endpointUrl);
    if (!u || !u.protocol || !u.host) {
        console.warn("Invalid origin");
        return;
    }

    console.warn("Origin url:", u);
    const origin = u.protocol + '//' + u.host;

    if (global.mxWidgetMessagingMessageEndpoints && global.mxWidgetMessagingMessageEndpoints.length > 0) {
        const length = global.mxWidgetMessagingMessageEndpoints.length;
        global.mxWidgetMessagingMessageEndpoints = global.mxWidgetMessagingMessageEndpoints.filter(function(endpoint) {
            return (endpoint.widgetId != widgetId || endpoint.endpointUrl != origin);
        });
        return (length > global.mxWidgetMessagingMessageEndpoints.length);
    }
    return false;
}

/**
 * Handle widget postMessage events
 * @param  {Event} event Event to handle
 * @return {undefined}
 */
function onMessage(event) {
    if (!event.origin) { // Handle chrome
        event.origin = event.originalEvent.origin;
    }

    // Event origin is empty string if undefined
    if (
        event.origin.length === 0 ||
        !trustedEndpoint(event.origin) ||
        event.data.api !== "widget" ||
        !event.data.widgetId
    ) {
        return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
    }

    const action = event.data.action;
    const widgetId = event.data.widgetId;
    if (action === 'content_loaded') {
        console.warn("Widget content loaded", widgetId);
        dis.dispatch({
            action: 'widget_content_loaded',
            widgetId: widgetId,
        });
        sendResponse(event, {success: true});
    } else if (action === 'supported_api_versions') {
        sendResponse(event, {
            api: "widget",
            supported_versions: SUPPORTED_WIDGET_API_VERSIONS,
        });
    } else if (action === 'api_version') {
        sendResponse(event, {
            api: "widget",
            version: WIDGET_API_VERSION,
        });
    } else {
        console.warn("Widget postMessage event unhandled");
        sendError(event, {message: "The postMessage was unhandled"});
    }
}

/**
 * Check if message origin is registered as trusted
 * @param  {string} origin PostMessage origin to check
 * @return {boolean}       True if trusted
 */
function trustedEndpoint(origin) {
    if (!origin) {
        return false;
    }

    return global.mxWidgetMessagingMessageEndpoints.some((endpoint) => {
        return endpoint.endpointUrl === origin;
    });
}

/**
 * Send a postmessage response to a postMessage request
 * @param  {Event} event  The original postMessage request event
 * @param  {Object} res   Response data
 */
function sendResponse(event, res) {
    const data = JSON.parse(JSON.stringify(event.data));
    data.response = res;
    event.source.postMessage(data, event.origin);
}

/**
 * Send an error response to a postMessage request
 * @param  {Event} event        The original postMessage request event
 * @param  {string} msg         Error message
 * @param  {Error} nestedError  Nested error event (optional)
 */
function sendError(event, msg, nestedError) {
    console.error("Action:" + event.data.action + " failed with message: " + msg);
    const data = JSON.parse(JSON.stringify(event.data));
    data.response = {
        error: {
            message: msg,
        },
    };
    if (nestedError) {
        data.response.error._error = nestedError;
    }
    event.source.postMessage(data, event.origin);
}

/**
 * Represents mapping of widget instance to URLs for trusted postMessage communication.
 */
class WidgetMessageEndpoint {
    /**
     * Mapping of widget instance to URL for trusted postMessage communication.
     * @param  {string} widgetId    Unique widget identifier
     * @param  {string} endpointUrl Widget wurl origin.
     */
    constructor(widgetId, endpointUrl) {
        if (!widgetId) {
            throw new Error("No widgetId specified in widgetMessageEndpoint constructor");
        }
        if (!endpointUrl) {
            throw new Error("No endpoint specified in widgetMessageEndpoint constructor");
        }
        this.widgetId = widgetId;
        this.endpointUrl = endpointUrl;
    }
}

export default {
    startListening: startListening,
    stopListening: stopListening,
    addEndpoint: addEndpoint,
    removeEndpoint: removeEndpoint,
};
<commit_msg>Remove logging.<commit_after>/*
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
Listens for incoming postMessage requests from embedded widgets. The following API is exposed:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID,
    data: {}
    // additional request fields
}

The complete request object is returned to the caller with an additional "response" key like so:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID,
    data: {},
    // additional request fields
    response: { ... }
}

The "api" field is required to use this API, and must be set to "widget" in all requests.

The "action" determines the format of the request and response. All actions can return an error response.

Additional data can be sent as additional, abritrary fields. However, typically the data object should be used.

A success response is an object with zero or more keys.

An error response is a "response" object which consists of a sole "error" key to indicate an error.
They look like:
{
    error: {
        message: "Unable to invite user into room.",
        _error: <Original Error Object>
    }
}
The "message" key should be a human-friendly string.

ACTIONS
=======
** All actions must include an "api" field with valie "widget".**
All actions can return an error response instead of the response outlined below.

content_loaded
--------------
Indicates that widget contet has fully loaded

Request:
 - widgetId is the unique ID of the widget instance in riot / matrix state.
 - No additional fields.
Response:
{
    success: true
}
Example:
{
    api: "widget",
    action: "content_loaded",
    widgetId: $WIDGET_ID
}

api_version
-----------
Get the current version of the widget postMessage API

Request:
 - No additional fields.
Response:
{
    api_version: "0.0.1"
}
Example:
{
    api: "widget",
    action: "api_version",
}

supported_api_versions
----------------------
Get versions of the widget postMessage API that are currently supported

Request:
 - No additional fields.
Response:
{
    api: "widget"
    supported_versions: ["0.0.1"]
}
Example:
{
    api: "widget",
    action: "supported_api_versions",
}

*/

import URL from 'url';

const WIDGET_API_VERSION = '0.0.1'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [
    '0.0.1',
];

import dis from './dispatcher';

if (!global.mxWidgetMessagingListenerCount) {
    global.mxWidgetMessagingListenerCount = 0;
}
if (!global.mxWidgetMessagingMessageEndpoints) {
    global.mxWidgetMessagingMessageEndpoints = [];
}

/**
 * Register widget message event listeners
 */
function startListening() {
    if (global.mxWidgetMessagingListenerCount === 0) {
        window.addEventListener("message", onMessage, false);
    }
    global.mxWidgetMessagingListenerCount += 1;
}

/**
 * De-register widget message event listeners
 */
function stopListening() {
    global.mxWidgetMessagingListenerCount -= 1;
    if (global.mxWidgetMessagingListenerCount === 0) {
        window.removeEventListener("message", onMessage);
    }
    if (global.mxWidgetMessagingListenerCount < 0) {
        // Make an error so we get a stack trace
        const e = new Error(
            "WidgetMessaging: mismatched startListening / stopListening detected." +
            " Negative count",
        );
        console.error(e);
    }
}

/**
 * Register a widget endpoint for trusted postMessage communication
 * @param {string} widgetId    Unique widget identifier
 * @param {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
 */
function addEndpoint(widgetId, endpointUrl) {
    const u = URL.parse(endpointUrl);
    if (!u || !u.protocol || !u.host) {
        console.warn("Invalid origin");
        return;
    }

    const origin = u.protocol + '//' + u.host;
    const endpoint = new WidgetMessageEndpoint(widgetId, origin);
    if (global.mxWidgetMessagingMessageEndpoints) {
        if (global.mxWidgetMessagingMessageEndpoints.some(function(ep) {
            return (ep.widgetId === widgetId && ep.endpointUrl === endpointUrl);
        })) {
            // Message endpoint already registered
            console.warn("Endpoint already registered");
            return;
        }
        global.mxWidgetMessagingMessageEndpoints.push(endpoint);
    }
}

/**
 * De-register a widget endpoint from trusted communication sources
 * @param  {string} widgetId Unique widget identifier
 * @param  {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
 * @return {boolean} True if endpoint was successfully removed
 */
function removeEndpoint(widgetId, endpointUrl) {
    const u = URL.parse(endpointUrl);
    if (!u || !u.protocol || !u.host) {
        console.warn("Invalid origin");
        return;
    }

    console.warn("Origin url:", u);
    const origin = u.protocol + '//' + u.host;

    if (global.mxWidgetMessagingMessageEndpoints && global.mxWidgetMessagingMessageEndpoints.length > 0) {
        const length = global.mxWidgetMessagingMessageEndpoints.length;
        global.mxWidgetMessagingMessageEndpoints = global.mxWidgetMessagingMessageEndpoints.filter(function(endpoint) {
            return (endpoint.widgetId != widgetId || endpoint.endpointUrl != origin);
        });
        return (length > global.mxWidgetMessagingMessageEndpoints.length);
    }
    return false;
}

/**
 * Handle widget postMessage events
 * @param  {Event} event Event to handle
 * @return {undefined}
 */
function onMessage(event) {
    if (!event.origin) { // Handle chrome
        event.origin = event.originalEvent.origin;
    }

    // Event origin is empty string if undefined
    if (
        event.origin.length === 0 ||
        !trustedEndpoint(event.origin) ||
        event.data.api !== "widget" ||
        !event.data.widgetId
    ) {
        return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
    }

    const action = event.data.action;
    const widgetId = event.data.widgetId;
    if (action === 'content_loaded') {
        dis.dispatch({
            action: 'widget_content_loaded',
            widgetId: widgetId,
        });
        sendResponse(event, {success: true});
    } else if (action === 'supported_api_versions') {
        sendResponse(event, {
            api: "widget",
            supported_versions: SUPPORTED_WIDGET_API_VERSIONS,
        });
    } else if (action === 'api_version') {
        sendResponse(event, {
            api: "widget",
            version: WIDGET_API_VERSION,
        });
    } else {
        console.warn("Widget postMessage event unhandled");
        sendError(event, {message: "The postMessage was unhandled"});
    }
}

/**
 * Check if message origin is registered as trusted
 * @param  {string} origin PostMessage origin to check
 * @return {boolean}       True if trusted
 */
function trustedEndpoint(origin) {
    if (!origin) {
        return false;
    }

    return global.mxWidgetMessagingMessageEndpoints.some((endpoint) => {
        return endpoint.endpointUrl === origin;
    });
}

/**
 * Send a postmessage response to a postMessage request
 * @param  {Event} event  The original postMessage request event
 * @param  {Object} res   Response data
 */
function sendResponse(event, res) {
    const data = JSON.parse(JSON.stringify(event.data));
    data.response = res;
    event.source.postMessage(data, event.origin);
}

/**
 * Send an error response to a postMessage request
 * @param  {Event} event        The original postMessage request event
 * @param  {string} msg         Error message
 * @param  {Error} nestedError  Nested error event (optional)
 */
function sendError(event, msg, nestedError) {
    console.error("Action:" + event.data.action + " failed with message: " + msg);
    const data = JSON.parse(JSON.stringify(event.data));
    data.response = {
        error: {
            message: msg,
        },
    };
    if (nestedError) {
        data.response.error._error = nestedError;
    }
    event.source.postMessage(data, event.origin);
}

/**
 * Represents mapping of widget instance to URLs for trusted postMessage communication.
 */
class WidgetMessageEndpoint {
    /**
     * Mapping of widget instance to URL for trusted postMessage communication.
     * @param  {string} widgetId    Unique widget identifier
     * @param  {string} endpointUrl Widget wurl origin.
     */
    constructor(widgetId, endpointUrl) {
        if (!widgetId) {
            throw new Error("No widgetId specified in widgetMessageEndpoint constructor");
        }
        if (!endpointUrl) {
            throw new Error("No endpoint specified in widgetMessageEndpoint constructor");
        }
        this.widgetId = widgetId;
        this.endpointUrl = endpointUrl;
    }
}

export default {
    startListening: startListening,
    stopListening: stopListening,
    addEndpoint: addEndpoint,
    removeEndpoint: removeEndpoint,
};
<|endoftext|>


<commit_before><commit_msg>memory item test passes<commit_after>'use strict';

const expect = require('chai').expect;

describe.only('Testing the Thumbnail Container Controller', function() { /* eslint-disable */
  beforeEach(done => {
    angular.mock.module('momentus');
    angular.mock.inject(($componentController, $rootScope) => {
      this.$componentController = $componentController;
      this.$rootScope = $rootScope;
      this.memoryItemCtrl = new $componentController('memoryItemController');
      this.memoryItemCtrl.$onInit();
      done();
    });
  });

  it('should have a title', done => {
    expect(this.memoryItemCtrl.showEditMemory).to.be.false;
    done();
  });
});<|endoftext|>