<commit_before>/**
 * @preserve
 * Sharer.js
 *
 * @description Create your own social share buttons
 * @version 0.4.0
 * @author Ellison Leao <ellisonleao@gmail.com>
 * @license GPLv3
 *
 */

;(function(window, document) {
    'use strict';
    /**
   * @constructor
   */
    var Sharer = function(elem) {
        this.elem = elem;
    };

    /**
   *  @function init
   *  @description bind the events for multiple sharer elements
   *  @returns {Empty}
   */
    Sharer.init = function() {
        var elems = document.querySelectorAll('[data-sharer]'),
            i,
            l = elems.length;

        for (i = 0; i < l; i++) {
            elems[i].addEventListener('click', Sharer.add);
        }
    };

    /**
   *  @function add
   *  @description bind the share event for a single dom element
   *  @returns {Empty}
   */
    Sharer.add = function(elem) {
        var target = elem.currentTarget || elem.srcElement;
        var sharer = new Sharer(target);
        sharer.share();
    };

    // instance methods
    Sharer.prototype = {
        constructor: Sharer,
        /**
     *  @function getValue
     *  @description Helper to get the attribute of a DOM element
     *  @param {String} attr DOM element attribute
     *  @returns {String|Empty} returns the attr value or empty string
     */
        getValue: function(attr) {
            var val = this.elem.getAttribute('data-' + attr);
            // handing facebook hashtag attribute
            if (val && attr === 'hashtag') {
                if (!val.startsWith('#')) {
                    val = '#' + val;
                }
            }
            return val;
        },

        /**
     * @event share
     * @description Main share event. Will pop a window or redirect to a link
     * based on the data-sharer attribute.
     */
        share: function() {
            var sharer = this.getValue('sharer').toLowerCase(),
                sharers = {
                    facebook: {
                        shareUrl: 'https://www.facebook.com/sharer/sharer.php',
                        params: {
                            u: this.getValue('url'),
                            hashtag: this.getValue('hashtag')
                        }
                    },
                    linkedin: {
                        shareUrl: 'https://www.linkedin.com/shareArticle',
                        params: {
                            url: this.getValue('url'),
                            mini: true
                        }
                    },
                    twitter: {
                        shareUrl: 'https://twitter.com/intent/tweet/',
                        params: {
                            text: this.getValue('title'),
                            url: this.getValue('url'),
                            hashtags: this.getValue('hashtags'),
                            via: this.getValue('via')
                        }
                    },
                    email: {
                        shareUrl: 'mailto:' + this.getValue('to') || '',
                        params: {
                            subject: this.getValue('subject'),
                            body: this.getValue('title') + '\n' + this.getValue('url')
                        },
                        isLink: true
                    },
                    whatsapp: {
                        shareUrl: this.getValue('web') !== null ? 'https://api.whatsapp.com/send' : 'whatsapp://send',
                        params: {
                            text: this.getValue('title') + ' ' + this.getValue('url')
                        },
                        isLink: true
                    },
                    telegram: {
                        shareUrl: this.getValue('web') !== null ? 'https://telegram.me/share' : 'tg://msg_url',
                        params: {
                            text: this.getValue('title'),
                            url: this.getValue('url'),
                            to: this.getValue('to')
                        },
                        isLink: true
                    },
                    viber: {
                        shareUrl: 'viber://forward',
                        params: {
                            text: this.getValue('title') + ' ' + this.getValue('url')
                        },
                        isLink: true
                    },
                    line: {
                        shareUrl:
              'http://line.me/R/msg/text/?' + encodeURIComponent(this.getValue('title') + ' ' + this.getValue('url')),
                        isLink: true
                    },
                    pinterest: {
                        shareUrl: 'https://www.pinterest.com/pin/create/button/',
                        params: {
                            url: this.getValue('url'),
                            media: this.getValue('image'),
                            description: this.getValue('description')
                        }
                    },
                    tumblr: {
                        shareUrl: 'http://tumblr.com/widgets/share/tool',
                        params: {
                            canonicalUrl: this.getValue('url'),
                            content: this.getValue('url'),
                            posttype: 'link',
                            title: this.getValue('title'),
                            caption: this.getValue('caption'),
                            tags: this.getValue('tags')
                        }
                    },
                    hackernews: {
                        shareUrl: 'https://news.ycombinator.com/submitlink',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title')
                        }
                    },
                    reddit: {
                        shareUrl: 'https://www.reddit.com/submit',
                        params: { url: this.getValue('url') }
                    },
                    vk: {
                        shareUrl: 'http://vk.com/share.php',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('caption'),
                            image: this.getValue('image')
                        }
                    },
                    xing: {
                        shareUrl: 'https://www.xing.com/app/user',
                        params: {
                            op: 'share',
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    buffer: {
                        shareUrl: 'https://buffer.com/add',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            via: this.getValue('via'),
                            picture: this.getValue('picture')
                        }
                    },
                    instapaper: {
                        shareUrl: 'http://www.instapaper.com/edit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    pocket: {
                        shareUrl: 'https://getpocket.com/save',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    stumbleupon: {
                        // Usage deprecated, leaving for backwards compatibility.
                        shareUrl: 'http://www.stumbleupon.com/submit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mashable: {
                        shareUrl: 'https://mashable.com/submit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mix: {
                        shareUrl: 'https://mix.com/add',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    flipboard: {
                        shareUrl: 'https://share.flipboard.com/bookmarklet/popout',
                        params: {
                            v: 2,
                            title: this.getValue('title'),
                            url: this.getValue('url'),
                            t: Date.now()
                        }
                    },
                    weibo: {
                        shareUrl: 'http://service.weibo.com/share/share.php',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            pic: this.getValue('image'),
                            appkey: this.getValue('appkey'),
                            ralateUid: this.getValue('ralateuid'),
                            language: 'zh_cn'
                        }
                    },
                    renren: {
                        shareUrl: 'http://share.renren.com/share/buttonshare',
                        params: {
                            link: this.getValue('url')
                        }
                    },
                    myspace: {
                        shareUrl: 'https://myspace.com/post',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title'),
                            c: this.getValue('description')
                        }
                    },
                    blogger: {
                        shareUrl: 'https://www.blogger.com/blog-this.g',
                        params: {
                            u: this.getValue('url'),
                            n: this.getValue('title'),
                            t: this.getValue('description')
                        }
                    },
                    baidu: {
                        shareUrl: 'http://cang.baidu.com/do/add',
                        params: {
                            it: this.getValue('title'),
                            iu: this.getValue('url')
                        }
                    },
                    douban: {
                        shareUrl: 'https://www.douban.com/share/service',
                        params: {
                            name: this.getValue('name'),
                            href: this.getValue('url'),
                            image: this.getValue('image'),
                            comment: this.getValue('description')
                        }
                    },
                    okru: {
                        shareUrl: 'https://connect.ok.ru/dk',
                        params: {
                            'st.cmd': 'WidgetSharePreview',
                            'st.shareUrl': this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mailru: {
                        shareUrl: 'http://connect.mail.ru/share',
                        params: {
                            share_url: this.getValue('url'),
                            linkname: this.getValue('title'),
                            linknote: this.getValue('description'),
                            type: 'page'
                        }
                    },
                    evernote: {
                        shareUrl: 'https://www.evernote.com/clip.action',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    skype: {
                        shareUrl: 'https://web.skype.com/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    quora: {
                        shareUrl: 'https://www.quora.com/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    delicious: {
                        shareUrl: 'https://del.icio.us/post',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    sms: {
                        shareUrl: 'sms://',
                        params: {
                            body: this.getValue('body')
                        }
                    },
                    trello: {
                        shareUrl: 'https://trello.com/add-card',
                        params: {
                            url: this.getValue('url'),
                            name: this.getValue('title'),
                            desc: this.getValue('description'),
                            mode: 'popup'
                        }
                    },
                    messenger: {
                        shareUrl: 'fb-messenger://share',
                        params: {
                            link: this.getValue('url')
                        }
                    },
                    odnoklassniki: {
                        shareUrl: 'https://connect.ok.ru/dk',
                        params: {
                            st: {
                                cmd: 'WidgetSharePreview',
                                deprecated: 1,
                                shareUrl: this.getValue('url')
                            }
                        }
                    },
                    meneame: {
                        shareUrl: 'https://www.meneame.net/submit',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    diaspora: {
                        shareUrl: 'https://share.diasporafoundation.org',
                        params: {
                            title: this.getValue('title'),
                            url: this.getValue('url')
                        }
                    },
                    googlebookmarks: {
                        shareUrl: 'https://www.google.com/bookmarks/mark',
                        params: {
                            op: 'edit',
                            bkmk: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    qzone: {
                        shareUrl: 'https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    refind: {
                        shareUrl: 'https://refind.com',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    surfingbird: {
                        shareUrl: 'https://surfingbird.ru/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    yahoomail: {
                        shareUrl: 'http://compose.mail.yahoo.com',
                        params: {
                            to: this.getValue('to'),
                            subject: this.getValue('subject'),
                            body: this.getValue('body')
                        }
                    },
                    wordpress: {
                        shareUrl: 'https://wordpress.com/wp-admin/press-this.php',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title'),
                            s: this.getValue('title')
                        }
                    },
                    amazon: {
                        shareUrl: 'https://www.amazon.com/gp/wishlist/static-add',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title')
                        }
                    },
                    pinboard: {
                        shareUrl: 'https://pinboard.in/add',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    threema: {
                        shareUrl: 'threema://compose',
                        params: {
                            text: this.getValue('text'),
                            id: this.getValue('id')
                        }
                    },
                    kakaostory: {
                        shareUrl: 'https://story.kakao.com/share',
                        params: {
                            url: this.getValue('url')
                        }
                    }
                },
                s = sharers[sharer];

            // custom popups sizes
            if (s) {
                s.width = this.getValue('width');
                s.height = this.getValue('height');
            }
            return s !== undefined ? this.urlSharer(s) : false;
        },
        /**
     * @event urlSharer
     * @param {Object} sharer
     */
        urlSharer: function(sharer) {
            var p = sharer.params || {},
                keys = Object.keys(p),
                i,
                str = keys.length > 0 ? '?' : '';
            for (i = 0; i < keys.length; i++) {
                if (str !== '?') {
                    str += '&';
                }
                if (p[keys[i]]) {
                    str += keys[i] + '=' + encodeURIComponent(p[keys[i]]);
                }
            }
            sharer.shareUrl += str;

            if (!sharer.isLink) {
                var popWidth = sharer.width || 600,
                    popHeight = sharer.height || 480,
                    left = window.innerWidth / 2 - popWidth / 2 + window.screenX,
                    top = window.innerHeight / 2 - popHeight / 2 + window.screenY,
                    popParams = 'scrollbars=no, width=' + popWidth + ', height=' + popHeight + ', top=' + top + ', left=' + left,
                    newWindow = window.open(sharer.shareUrl, '', popParams);

                if (window.focus) {
                    newWindow.focus();
                }
            } else {
                window.location.href = sharer.shareUrl;
            }
        }
    };

    // adding sharer events on domcontentload
    if (document.readyState === 'complete' || document.readyState !== 'loading') {
        Sharer.init();
    } else {
        document.addEventListener('DOMContentLoaded', Sharer.init);
    }

    // exporting sharer for external usage
    window.Sharer = Sharer;
})(window, document);
<commit_msg>removing telegram 'to' param. Close #119<commit_after>/**
 * @preserve
 * Sharer.js
 *
 * @description Create your own social share buttons
 * @version 0.4.0
 * @author Ellison Leao <ellisonleao@gmail.com>
 * @license GPLv3
 *
 */

;(function(window, document) {
    'use strict';
    /**
   * @constructor
   */
    var Sharer = function(elem) {
        this.elem = elem;
    };

    /**
   *  @function init
   *  @description bind the events for multiple sharer elements
   *  @returns {Empty}
   */
    Sharer.init = function() {
        var elems = document.querySelectorAll('[data-sharer]'),
            i,
            l = elems.length;

        for (i = 0; i < l; i++) {
            elems[i].addEventListener('click', Sharer.add);
        }
    };

    /**
   *  @function add
   *  @description bind the share event for a single dom element
   *  @returns {Empty}
   */
    Sharer.add = function(elem) {
        var target = elem.currentTarget || elem.srcElement;
        var sharer = new Sharer(target);
        sharer.share();
    };

    // instance methods
    Sharer.prototype = {
        constructor: Sharer,
        /**
     *  @function getValue
     *  @description Helper to get the attribute of a DOM element
     *  @param {String} attr DOM element attribute
     *  @returns {String|Empty} returns the attr value or empty string
     */
        getValue: function(attr) {
            var val = this.elem.getAttribute('data-' + attr);
            // handing facebook hashtag attribute
            if (val && attr === 'hashtag') {
                if (!val.startsWith('#')) {
                    val = '#' + val;
                }
            }
            return val;
        },

        /**
     * @event share
     * @description Main share event. Will pop a window or redirect to a link
     * based on the data-sharer attribute.
     */
        share: function() {
            var sharer = this.getValue('sharer').toLowerCase(),
                sharers = {
                    facebook: {
                        shareUrl: 'https://www.facebook.com/sharer/sharer.php',
                        params: {
                            u: this.getValue('url'),
                            hashtag: this.getValue('hashtag')
                        }
                    },
                    linkedin: {
                        shareUrl: 'https://www.linkedin.com/shareArticle',
                        params: {
                            url: this.getValue('url'),
                            mini: true
                        }
                    },
                    twitter: {
                        shareUrl: 'https://twitter.com/intent/tweet/',
                        params: {
                            text: this.getValue('title'),
                            url: this.getValue('url'),
                            hashtags: this.getValue('hashtags'),
                            via: this.getValue('via')
                        }
                    },
                    email: {
                        shareUrl: 'mailto:' + this.getValue('to') || '',
                        params: {
                            subject: this.getValue('subject'),
                            body: this.getValue('title') + '\n' + this.getValue('url')
                        },
                        isLink: true
                    },
                    whatsapp: {
                        shareUrl: this.getValue('web') !== null ? 'https://api.whatsapp.com/send' : 'whatsapp://send',
                        params: {
                            text: this.getValue('title') + ' ' + this.getValue('url')
                        },
                        isLink: true
                    },
                    telegram: {
                        shareUrl: this.getValue('web') !== null ? 'https://telegram.me/share' : 'tg://msg_url',
                        params: {
                            text: this.getValue('title'),
                            url: this.getValue('url')
                        },
                        isLink: true
                    },
                    viber: {
                        shareUrl: 'viber://forward',
                        params: {
                            text: this.getValue('title') + ' ' + this.getValue('url')
                        },
                        isLink: true
                    },
                    line: {
                        shareUrl:
              'http://line.me/R/msg/text/?' + encodeURIComponent(this.getValue('title') + ' ' + this.getValue('url')),
                        isLink: true
                    },
                    pinterest: {
                        shareUrl: 'https://www.pinterest.com/pin/create/button/',
                        params: {
                            url: this.getValue('url'),
                            media: this.getValue('image'),
                            description: this.getValue('description')
                        }
                    },
                    tumblr: {
                        shareUrl: 'http://tumblr.com/widgets/share/tool',
                        params: {
                            canonicalUrl: this.getValue('url'),
                            content: this.getValue('url'),
                            posttype: 'link',
                            title: this.getValue('title'),
                            caption: this.getValue('caption'),
                            tags: this.getValue('tags')
                        }
                    },
                    hackernews: {
                        shareUrl: 'https://news.ycombinator.com/submitlink',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title')
                        }
                    },
                    reddit: {
                        shareUrl: 'https://www.reddit.com/submit',
                        params: { url: this.getValue('url') }
                    },
                    vk: {
                        shareUrl: 'http://vk.com/share.php',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('caption'),
                            image: this.getValue('image')
                        }
                    },
                    xing: {
                        shareUrl: 'https://www.xing.com/app/user',
                        params: {
                            op: 'share',
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    buffer: {
                        shareUrl: 'https://buffer.com/add',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            via: this.getValue('via'),
                            picture: this.getValue('picture')
                        }
                    },
                    instapaper: {
                        shareUrl: 'http://www.instapaper.com/edit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    pocket: {
                        shareUrl: 'https://getpocket.com/save',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    stumbleupon: {
                        // Usage deprecated, leaving for backwards compatibility.
                        shareUrl: 'http://www.stumbleupon.com/submit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mashable: {
                        shareUrl: 'https://mashable.com/submit',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mix: {
                        shareUrl: 'https://mix.com/add',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    flipboard: {
                        shareUrl: 'https://share.flipboard.com/bookmarklet/popout',
                        params: {
                            v: 2,
                            title: this.getValue('title'),
                            url: this.getValue('url'),
                            t: Date.now()
                        }
                    },
                    weibo: {
                        shareUrl: 'http://service.weibo.com/share/share.php',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            pic: this.getValue('image'),
                            appkey: this.getValue('appkey'),
                            ralateUid: this.getValue('ralateuid'),
                            language: 'zh_cn'
                        }
                    },
                    renren: {
                        shareUrl: 'http://share.renren.com/share/buttonshare',
                        params: {
                            link: this.getValue('url')
                        }
                    },
                    myspace: {
                        shareUrl: 'https://myspace.com/post',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title'),
                            c: this.getValue('description')
                        }
                    },
                    blogger: {
                        shareUrl: 'https://www.blogger.com/blog-this.g',
                        params: {
                            u: this.getValue('url'),
                            n: this.getValue('title'),
                            t: this.getValue('description')
                        }
                    },
                    baidu: {
                        shareUrl: 'http://cang.baidu.com/do/add',
                        params: {
                            it: this.getValue('title'),
                            iu: this.getValue('url')
                        }
                    },
                    douban: {
                        shareUrl: 'https://www.douban.com/share/service',
                        params: {
                            name: this.getValue('name'),
                            href: this.getValue('url'),
                            image: this.getValue('image'),
                            comment: this.getValue('description')
                        }
                    },
                    okru: {
                        shareUrl: 'https://connect.ok.ru/dk',
                        params: {
                            'st.cmd': 'WidgetSharePreview',
                            'st.shareUrl': this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    mailru: {
                        shareUrl: 'http://connect.mail.ru/share',
                        params: {
                            share_url: this.getValue('url'),
                            linkname: this.getValue('title'),
                            linknote: this.getValue('description'),
                            type: 'page'
                        }
                    },
                    evernote: {
                        shareUrl: 'https://www.evernote.com/clip.action',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    skype: {
                        shareUrl: 'https://web.skype.com/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    quora: {
                        shareUrl: 'https://www.quora.com/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    delicious: {
                        shareUrl: 'https://del.icio.us/post',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    sms: {
                        shareUrl: 'sms://',
                        params: {
                            body: this.getValue('body')
                        }
                    },
                    trello: {
                        shareUrl: 'https://trello.com/add-card',
                        params: {
                            url: this.getValue('url'),
                            name: this.getValue('title'),
                            desc: this.getValue('description'),
                            mode: 'popup'
                        }
                    },
                    messenger: {
                        shareUrl: 'fb-messenger://share',
                        params: {
                            link: this.getValue('url')
                        }
                    },
                    odnoklassniki: {
                        shareUrl: 'https://connect.ok.ru/dk',
                        params: {
                            st: {
                                cmd: 'WidgetSharePreview',
                                deprecated: 1,
                                shareUrl: this.getValue('url')
                            }
                        }
                    },
                    meneame: {
                        shareUrl: 'https://www.meneame.net/submit',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    diaspora: {
                        shareUrl: 'https://share.diasporafoundation.org',
                        params: {
                            title: this.getValue('title'),
                            url: this.getValue('url')
                        }
                    },
                    googlebookmarks: {
                        shareUrl: 'https://www.google.com/bookmarks/mark',
                        params: {
                            op: 'edit',
                            bkmk: this.getValue('url'),
                            title: this.getValue('title')
                        }
                    },
                    qzone: {
                        shareUrl: 'https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    refind: {
                        shareUrl: 'https://refind.com',
                        params: {
                            url: this.getValue('url')
                        }
                    },
                    surfingbird: {
                        shareUrl: 'https://surfingbird.ru/share',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    yahoomail: {
                        shareUrl: 'http://compose.mail.yahoo.com',
                        params: {
                            to: this.getValue('to'),
                            subject: this.getValue('subject'),
                            body: this.getValue('body')
                        }
                    },
                    wordpress: {
                        shareUrl: 'https://wordpress.com/wp-admin/press-this.php',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title'),
                            s: this.getValue('title')
                        }
                    },
                    amazon: {
                        shareUrl: 'https://www.amazon.com/gp/wishlist/static-add',
                        params: {
                            u: this.getValue('url'),
                            t: this.getValue('title')
                        }
                    },
                    pinboard: {
                        shareUrl: 'https://pinboard.in/add',
                        params: {
                            url: this.getValue('url'),
                            title: this.getValue('title'),
                            description: this.getValue('description')
                        }
                    },
                    threema: {
                        shareUrl: 'threema://compose',
                        params: {
                            text: this.getValue('text'),
                            id: this.getValue('id')
                        }
                    },
                    kakaostory: {
                        shareUrl: 'https://story.kakao.com/share',
                        params: {
                            url: this.getValue('url')
                        }
                    }
                },
                s = sharers[sharer];

            // custom popups sizes
            if (s) {
                s.width = this.getValue('width');
                s.height = this.getValue('height');
            }
            return s !== undefined ? this.urlSharer(s) : false;
        },
        /**
     * @event urlSharer
     * @param {Object} sharer
     */
        urlSharer: function(sharer) {
            var p = sharer.params || {},
                keys = Object.keys(p),
                i,
                str = keys.length > 0 ? '?' : '';
            for (i = 0; i < keys.length; i++) {
                if (str !== '?') {
                    str += '&';
                }
                if (p[keys[i]]) {
                    str += keys[i] + '=' + encodeURIComponent(p[keys[i]]);
                }
            }
            sharer.shareUrl += str;

            if (!sharer.isLink) {
                var popWidth = sharer.width || 600,
                    popHeight = sharer.height || 480,
                    left = window.innerWidth / 2 - popWidth / 2 + window.screenX,
                    top = window.innerHeight / 2 - popHeight / 2 + window.screenY,
                    popParams = 'scrollbars=no, width=' + popWidth + ', height=' + popHeight + ', top=' + top + ', left=' + left,
                    newWindow = window.open(sharer.shareUrl, '', popParams);

                if (window.focus) {
                    newWindow.focus();
                }
            } else {
                window.location.href = sharer.shareUrl;
            }
        }
    };

    // adding sharer events on domcontentload
    if (document.readyState === 'complete' || document.readyState !== 'loading') {
        Sharer.init();
    } else {
        document.addEventListener('DOMContentLoaded', Sharer.init);
    }

    // exporting sharer for external usage
    window.Sharer = Sharer;
})(window, document);
<|endoftext|>


<commit_before>var exec = require('child_process').exec;
var path = require('path');

const DBUS_COMMAND = "bash "+__dirname+"/dbus.sh ";
const DBUS_DEST_DEFAULT = 'org.mpris.MediaPlayer2.omxplayer';

const MAX_START_ATTEMPTS = 300;

class OmxInstance {

	constructor(options) {
		this.options = options;

		if (options && typeof options.layer === 'number') {
			this.dbusDest = DBUS_DEST_DEFAULT + '_layer' + options.layer;
			this.layer = options.layer;
			console.log('setup for layered mode');
		} else {
			this.dbusDest = DBUS_DEST_DEFAULT;
			this.layer = 0;
			console.log('not layered mode');
		}
		console.log('dbus name will be', this.dbusDest);

		exec('mkfifo omxpipe'+this.layer);

		if (options && options.progressInterval) {
			console.log('custom progress interval:', options.progressInterval);
			this.progressInterval = options.progressInterval;
		} else {
			this.progressInterval = 1000;
		}

		this.progressHandler = null;

	}

	getLayer() {
		return this.layer;
	}

	cancelProgressHandlerIfActive() {
		if (this.progressHandler) {
			clearInterval(this.progressHandler);
			console.info('progressHandler cancelled');
		}
	}

	dbusCommand (command)  {
		let merge = "bash " +__dirname+"/dbus.sh " + this.dbusDest + " " + command;
		if (command != 'getplaystatus' && command !='getvolume' && command != 'getposition') { console.log('merge:', merge); }
		return merge;
	}

	resume () {
		exec(this.dbusCommand('play'), (error, stdout, stderr) => {
			if (error) console.error('resume() error:', error);
			console.log('should play/resume/restart');
		});
	}

	pause () {
		exec(this.dbusCommand('pause'), (error, stdout, stderr) => {
			if (error) console.error('pause() error:', error);
			console.log('should pause');
		});
	}

	stop (callback) {
		exec(this.dbusCommand('stop'), (error, stdout, stderr) => {
			if (error) console.error('stop() error:', error);
			console.log('should stop');
			this.cancelProgressHandlerIfActive();
			if (callback) callback();
		});
	}

	quit (callback) {
		exec(this.dbusCommand('quit'), (error, stdout, stderr) => {
			this.cancelProgressHandlerIfActive();
			if (callback) callback();
	  });
	}

	seekRelative (offset) {
		//seek offset in seconds; relative from current position; negative values will cause a jump back;
		exec(this.dbusCommand('seek ' +Math.round(offset*1000000)), (error, stdout, stderr) => {});
	}

	setAbsolute (position) {
		//position in seconds from start; //positions larger than the duration will stop the player;
		exec(this.dbusCommand('setposition '+Math.round(position*1000000)), (error, stdout, stderr) => {
			if (error) console.error('setAbsolute() error:', error);
		});
	}

	setVolume (volume) {
		// volume range [0.0, 1.0];
		if (volume > 0 && volume < 1.0) {
			exec(this.dbusCommand('setvolume '+volume), (error, stdout, stderr) => {});
		}
	}

	setVisibility (visible) {
		let command = visible ? 'unhidevideo' : 'hidevideo';
		exec(this.dbusCommand(command), (err, stdout, stderr) => {});
	}

	setAlpha (alpha) {
		exec(this.dbusCommand('setalpha ' + alpha), (err, stdout, stderr) => {});
	}

	getCurrentPosition () {
		return new Promise( (resolve, reject) => {
				exec(this.dbusCommand('getposition'), (error, stdout, stderr) => {
					if (error) reject();
					let position = parseInt(stdout) / 1000; // microseconds to milliseconds
					resolve(position);
				});
		});
	}

	getPlayStatus () {
		return new Promise( (resolve, reject) => {
			exec(this.dbusCommand('getplaystatus'), (error, stdout, stderr) => {
				if (error) {
					console.error('error from getplaystatus:', error);
					reject(error);
				}
				if (stdout.indexOf('Playing') > -1) {
					resolve('playing');
				} else {
					resolve('paused');
				}
			});
		});
	}

	getDuration () {
		return new Promise( (resolve, reject) => {
			if (this.cachedDuration) {
				resolve(this.cachedDuration);
			} else {
				exec( this.dbusCommand('getduration'), (error, stdout, stderr) => {
					if (error) reject();

					let duration = parseInt(stdout) / 1000; // microseconds to milliseconds
					resolve(duration);
					this.cachedDuration = duration; // cache last known duration
				});
			}
		});
	}

	getVolume () {
		exec(this.dbusCommand('getvolume'), (error, stdout, stderr) => {
			if (error) return null;
			let volume = parseFloat(stdout);
			console.log('getVolume:', volume);
		});	}

	onProgress (callback) {
		console.log('add new progress handler for layer', this.layer);
		this.progressHandler = setInterval( () => {
			if (this.shouldBePlaying) {
				this.getPlayStatus()
				.then( (playStatus) => {
					this.getCurrentPosition()
					.then( (position) => {
						this.getDuration()
						.then( (duration) => {
							callback({ position: position, duration: duration, status: playStatus });
						});
					});
				})
				.catch( (err) => {
					console.warn('error getting playStatus:', err);
					// do NOT send error via callback!
				});
			}
		}, this.progressInterval);
	}

	onStart (callback) {
		console.log('onStart event');
		if (callback) {
			callback();
		}
	}

	waitTillPlaying (callback) {
		console.log('waitTillPlaying()');
		return new Promise( (resolve, reject) => {
			let countAttempts = 0;
			let interval = setInterval( () => {
				countAttempts++;
				exec(this.dbusCommand('getplaystatus'), (error, stdout, stderr) => {
					if (error) {
						if (countAttempts > MAX_START_ATTEMPTS) {
							console.error(`too many attempts (${countAttempts}) to get playstatus after start!`);
							clearInterval(interval);
							reject(error);
						}
					} else {
						console.info(`getplaystatus success after ${countAttempts} attempts: ${stdout}`);
						clearInterval(interval);
						resolve();
					}
				});
			}, 10);
		});
	}

	open (path, doneCallback, holdMode) {
		console.log('OmxInstance open() for layer #', this.layer, 'holdMode?', holdMode);
		let settings = this.options || {};
		let args = [];
		let command = 'omxplayer';

		args.push('"'+path+'"');

		if (['hdmi','local','both'].indexOf(settings.audioOutput) != -1) {
			args.push('-o');
			args.push(settings.audioOutput);
		}

		if (settings.blackBackground == true) { // defaults to false
			args.push('-b');
		}

		if (settings.backgroundARGB) {
			args.push('-b0x' + settings.backgroundARGB);
		}

		if (settings.disableKeys === true) { //defaults to  false
			args.push('--no-keys')
		}

		if (settings.disableOnScreenDisplay === true) { //defaults to  false
			args.push('--no-osd')
		}

		if (settings.disableGhostbox === true) { //defaults to  false
			args.push('--no-ghost-box');
		}

		if (settings.loop === true) { // defaults to false
			args.push('--loop');
		}

		if (settings.subtitlePath && settings.subtitlePath != "" ){
			args.push('--subtitles');
			args.push('"'+settings.subtitlePath+'"');
		}

		if (settings.startAt){
			args.push('--pos');
			args.push(''+settings.startAt+'');
		}

		if (settings.layer) {
			args.push('--layer');
			args.push(settings.layer);
		}

		if (holdMode) {
			args.push('--alpha');
			args.push(0);
		}

		args.push('--dbus_name');
		args.push(this.dbusDest);

		if (settings.aspectMode) {
			args.push('--aspect-mode');
			args.push(settings.aspectMode);
		}

		let finalOpenCommand = command+' '+args.join(' ')+' < omxpipe'+this.layer;
		console.log('finalOpenCommand:', finalOpenCommand);

		if (this.shouldBePlaying) {
			console.error('omx-layers was instructed to open, but playback is (?) already in progress');
		} else {

			exec(finalOpenCommand, (error, stdout, stderr) => {
				// This block executes on clip end...
				this.cancelProgressHandlerIfActive();
				if (doneCallback) doneCallback();
				console.log('omxpipe done for layer', this.layer);
				console.log(`final output from omxplayer: \n${stdout}\n.`);
				this.shouldBePlaying = false;
				this.cachedDuration = null;
			});

			exec(' . > omxpipe'+this.layer, (error, stdout, stderr) => {
				// This block executes as soon as pipe is ready...
				this.waitTillPlaying()
					.then( () => {
						console.info('confirmed started ok');
						this.shouldBePlaying = true;
						this.onStart(); // apply callback
						if (holdMode) {
							console.log('holdMode ON, so immediately pause and hide');
							this.pause();
							this.setVisibility(false);
						}
					})
					.catch ( () => {
						console.error('failed to confirm clip start!');
					});
			});
		}

	}

}

module.exports = OmxInstance;
<commit_msg>isBusy will return true if (this.shouldBePlaying); use this to avoid registering progress handlers when omx-layers instance is actually already playing or trying to start playback<commit_after>var exec = require('child_process').exec;
var path = require('path');

const DBUS_COMMAND = "bash "+__dirname+"/dbus.sh ";
const DBUS_DEST_DEFAULT = 'org.mpris.MediaPlayer2.omxplayer';

const MAX_START_ATTEMPTS = 300;

class OmxInstance {

	constructor(options) {
		this.options = options;

		if (options && typeof options.layer === 'number') {
			this.dbusDest = DBUS_DEST_DEFAULT + '_layer' + options.layer;
			this.layer = options.layer;
			console.log('setup for layered mode');
		} else {
			this.dbusDest = DBUS_DEST_DEFAULT;
			this.layer = 0;
			console.log('not layered mode');
		}
		console.log('dbus name will be', this.dbusDest);

		exec('mkfifo omxpipe'+this.layer);

		if (options && options.progressInterval) {
			console.log('custom progress interval:', options.progressInterval);
			this.progressInterval = options.progressInterval;
		} else {
			this.progressInterval = 1000;
		}

		this.progressHandler = null;

	}

	getLayer() {
		return this.layer;
	}

	cancelProgressHandlerIfActive() {
		if (this.progressHandler) {
			clearInterval(this.progressHandler);
			console.info('progressHandler cancelled');
			this.progressHandler = null;
		}
	}

	dbusCommand (command)  {
		let merge = "bash " +__dirname+"/dbus.sh " + this.dbusDest + " " + command;
		if (command != 'getplaystatus' && command !='getvolume' && command != 'getposition') { console.log('merge:', merge); }
		return merge;
	}

	resume () {
		exec(this.dbusCommand('play'), (error, stdout, stderr) => {
			if (error) console.error('resume() error:', error);
			console.log('should play/resume/restart');
		});
	}

	pause () {
		exec(this.dbusCommand('pause'), (error, stdout, stderr) => {
			if (error) console.error('pause() error:', error);
			console.log('should pause');
		});
	}

	stop (callback) {
		exec(this.dbusCommand('stop'), (error, stdout, stderr) => {
			if (error) console.error('stop() error:', error);
			console.log('should stop');
			this.cancelProgressHandlerIfActive();
			if (callback) callback();
		});
	}

	quit (callback) {
		exec(this.dbusCommand('quit'), (error, stdout, stderr) => {
			this.cancelProgressHandlerIfActive();
			if (callback) callback();
	  });
	}

	seekRelative (offset) {
		//seek offset in seconds; relative from current position; negative values will cause a jump back;
		exec(this.dbusCommand('seek ' +Math.round(offset*1000000)), (error, stdout, stderr) => {});
	}

	setAbsolute (position) {
		//position in seconds from start; //positions larger than the duration will stop the player;
		exec(this.dbusCommand('setposition '+Math.round(position*1000000)), (error, stdout, stderr) => {
			if (error) console.error('setAbsolute() error:', error);
		});
	}

	setVolume (volume) {
		// volume range [0.0, 1.0];
		if (volume > 0 && volume < 1.0) {
			exec(this.dbusCommand('setvolume '+volume), (error, stdout, stderr) => {});
		}
	}

	setVisibility (visible) {
		let command = visible ? 'unhidevideo' : 'hidevideo';
		exec(this.dbusCommand(command), (err, stdout, stderr) => {});
	}

	setAlpha (alpha) {
		exec(this.dbusCommand('setalpha ' + alpha), (err, stdout, stderr) => {});
	}

	getCurrentPosition () {
		return new Promise( (resolve, reject) => {
				exec(this.dbusCommand('getposition'), (error, stdout, stderr) => {
					if (error) reject();
					let position = parseInt(stdout) / 1000; // microseconds to milliseconds
					resolve(position);
				});
		});
	}

	getPlayStatus () {
		return new Promise( (resolve, reject) => {
			exec(this.dbusCommand('getplaystatus'), (error, stdout, stderr) => {
				if (error) {
					console.error('getPlayStatus() error:', error);
					reject(error);
				}
				if (stdout.indexOf('Playing') > -1) {
					resolve('playing');
				} else {
					resolve('paused');
				}
			});
		});
	}

	getDuration () {
		return new Promise( (resolve, reject) => {
			if (this.cachedDuration) {
				resolve(this.cachedDuration);
			} else {
				exec( this.dbusCommand('getduration'), (error, stdout, stderr) => {
					if (error) reject();

					let duration = parseInt(stdout) / 1000; // microseconds to milliseconds
					resolve(duration);
					this.cachedDuration = duration; // cache last known duration
				});
			}
		});
	}

	getVolume () {
		exec(this.dbusCommand('getvolume'), (error, stdout, stderr) => {
			if (error) return null;
			let volume = parseFloat(stdout);
			console.log('getVolume:', volume);
		});	}

	onProgress (callback) {
		console.log('add new progress handler for layer', this.layer);
		this.progressHandler = setInterval( () => {
			if (this.shouldBePlaying) {
				this.getPlayStatus()
				.then( (playStatus) => {
					this.getCurrentPosition()
					.then( (position) => {
						this.getDuration()
						.then( (duration) => {
							callback({ position: position, duration: duration, status: playStatus });
						});
					});
				})
				.catch( (err) => {
					console.warn('onProgress: error getting playStatus:', err);
					// do NOT send error via callback!
				});
			}
		}, this.progressInterval);
	}

	onStart (callback) {
		console.log('onStart event');
		if (callback) {
			callback();
		}
	}

	isBusy() {
		return this.shouldBePlaying;
	}

	waitTillPlaying (callback) {
		console.log('waitTillPlaying()');
		return new Promise( (resolve, reject) => {
			let countAttempts = 0;
			let interval = setInterval( () => {
				countAttempts++;
				exec(this.dbusCommand('getplaystatus'), (error, stdout, stderr) => {
					if (error) {
						if (countAttempts > MAX_START_ATTEMPTS) {
							console.error(`too many attempts (${countAttempts}) to get playstatus after start!`);
							clearInterval(interval);
							reject(error);
						}
					} else {
						console.info(`getplaystatus success after ${countAttempts} attempts: ${stdout}`);
						clearInterval(interval);
						resolve();
					}
				});
			}, 10);
		});
	}

	open (path, doneCallback, holdMode) {
		console.log('OmxInstance open() for layer #', this.layer, 'holdMode?', holdMode);
		let settings = this.options || {};
		let args = [];
		let command = 'omxplayer';

		args.push('"'+path+'"');

		if (['hdmi','local','both'].indexOf(settings.audioOutput) != -1) {
			args.push('-o');
			args.push(settings.audioOutput);
		}

		if (settings.blackBackground == true) { // defaults to false
			args.push('-b');
		}

		if (settings.backgroundARGB) {
			args.push('-b0x' + settings.backgroundARGB);
		}

		if (settings.disableKeys === true) { //defaults to  false
			args.push('--no-keys')
		}

		if (settings.disableOnScreenDisplay === true) { //defaults to  false
			args.push('--no-osd')
		}

		if (settings.disableGhostbox === true) { //defaults to  false
			args.push('--no-ghost-box');
		}

		if (settings.loop === true) { // defaults to false
			args.push('--loop');
		}

		if (settings.subtitlePath && settings.subtitlePath != "" ){
			args.push('--subtitles');
			args.push('"'+settings.subtitlePath+'"');
		}

		if (settings.startAt){
			args.push('--pos');
			args.push(''+settings.startAt+'');
		}

		if (settings.layer) {
			args.push('--layer');
			args.push(settings.layer);
		}

		if (holdMode) {
			args.push('--alpha');
			args.push(0);
		}

		args.push('--dbus_name');
		args.push(this.dbusDest);

		if (settings.aspectMode) {
			args.push('--aspect-mode');
			args.push(settings.aspectMode);
		}

		let finalOpenCommand = command+' '+args.join(' ')+' < omxpipe'+this.layer;
		console.log('finalOpenCommand:', finalOpenCommand);

		if (this.shouldBePlaying) {
			console.error('omx-layers was instructed to open, but playback is (?) already in progress');
		} else {

			exec(finalOpenCommand, (error, stdout, stderr) => {
				// This block executes on clip end...
				this.cancelProgressHandlerIfActive();
				if (doneCallback) doneCallback();
				console.log('omxpipe done for layer', this.layer);
				console.log(`final output from omxplayer: \n${stdout}\n.`);
				this.shouldBePlaying = false;
				this.cachedDuration = null;
			});

			exec(' . > omxpipe'+this.layer, (error, stdout, stderr) => {
				// This block executes as soon as pipe is ready...
				this.waitTillPlaying()
					.then( () => {
						console.info('confirmed started ok');
						this.shouldBePlaying = true;
						this.onStart(); // apply callback
						if (holdMode) {
							console.log('holdMode ON, so immediately pause and hide');
							this.pause();
							this.setVisibility(false);
						}
					})
					.catch ( () => {
						console.error('failed to confirm clip start!');
					});
			});
		}

	}

}

module.exports = OmxInstance;
<|endoftext|>


<commit_before>'use strict';

const co = require('co');

/**
 * Contains all timers.
 * @type {{}}
 */
const timers = {};

/**
 * Timers factory object.
 * @param name
 * @returns {*}
 */
const timer = function (name) {
    if (typeof timers[name] === 'undefined') {
        timers[name] = {
            ticks: [],

            /**
             * Get the median of all ticks.
             * @returns {*}
             */
            median: function () {
                if (this.ticks.length > 1) {
                    this.ticks.sort(function (a, b) {
                        return a && b && (a.getDiff() - b.getDiff()) || 0;
                    });

                    const l = this.ticks.length;
                    const half = Math.floor(l / 2);

                    if (l % 2) {
                        return this.ticks[half].getDiff();
                    } else {
                        return (this.ticks[half - 1].getDiff() + this.ticks[half].getDiff()) / 2;
                    }
                } else {
                    return this.ticks[0].getDiff();
                }
            },

            /**
             * Get the average duration of all ticks.
             * @returns {number}
             */
            mean: function () {
                return this.duration() / this.ticks.length;
            },

            /**
             * Get the duration of all ticks.
             * @returns {number}
             */
            duration: function () {
                let sum = 0;
                for (let i = 0, l = this.ticks.length; i < l; i++) {
                    sum += this.ticks[i].getDiff();
                }
                return sum;
            },

            /**
             * Get the shortest tick.
             * @returns {number}
             */
            min: function () {
                let min = this.ticks[0].getDiff();
                this.ticks.forEach(function (tick) {
                    if (tick.getDiff() < min) {
                        min = tick.getDiff();
                    }
                });

                return min;
            },

            /**
             * Get the longest tick.
             * @returns {number}
             */
            max: function () {
                let max = 0;
                this.ticks.forEach(function (tick) {
                    if (tick.getDiff() > max) {
                        max = tick.getDiff();
                    }
                });

                return max;
            },

            /**
             * Get the number of ticks.
             * @returns {Number}
             */
            count: function () {
                return Object.keys(this.ticks).length;
            },

            /**
             * Parse the numbers nicely.
             * @param num
             * @returns {string}
             */
            parse: function (num) {
                if (num < 1e3) {
                    return num + 'ns';
                } else if (num >= 1e3 && num < 1e6) {
                    return num / 1e3 + 'us';
                } else if (num >= 1e6 && num < 1e9) {
                    return num / 1e6 + 'ms';
                } else if (num >= 1e9) {
                    return num / 1e9 + 's';
                }
            }
        };
    }

    return timers[name];
};

/**
 * Check if `obj` is a generator function.
 *
 * @param {Mixed} obj
 * @return {Boolean}
 * @api private
 */
function isGeneratorFunction(obj) {
    var constructor = obj.constructor;

    if (!constructor) {
        return false;
    }

    if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) {
        return true;
    }

    return 'function' === typeof constructor.prototype.next && 'function' === typeof constructor.prototype.throw;
}

/**
 * Constructor of tick.
 * @param name The name of this tick.
 * @returns {Tick}
 * @constructor
 */
function Tick(name) {
    this.name = name;
    return this;
}

Tick.wrap = function (name, callback) {
    if (typeof name === 'function') {
        callback = name;
        name = functionName(callback);
    }

    if (name === '') {
        name = 'anon';
    }

    const tick = new Tick(name);
    tick.start();

    const done = function () {
        tick.stop();
    };

    if (isGeneratorFunction(callback)) {
        co(callback).then(done, done);
    } else if(!!callback.toString().match(/^function.*\(.*\)/)) {
        // If done is passed when the callback is declared than we assume is async
        callback(done);
    } else {
        // Otherwise just call the function and stop the tick
        callback();
        tick.stop();
    }

    return tick;
};

/**
 * Starts the tick.
 */
Tick.prototype.start = function () {
    this.hrstart = process.hrtime();
};

/**
 * Ends the tick.
 */
Tick.prototype.stop = function () {
    this.hrend = process.hrtime(this.hrstart);
    timer(this.name).ticks.push(this);
};

/**
 * Get the duration of the tick.
 * @returns Long nanoseconds
 */
Tick.prototype.getDiff = function () {
    return this.hrend[0] * 1e9 + this.hrend[1];
};

module.exports = {
    timer: timer,
    timers: timers,
    Tick: Tick
};

/**
 * Helper function used to retrieve function name.
 * @param fun
 * @returns {string}
 */
function functionName(fun) {
    let ret = fun.toString();
    ret = ret.substr('function '.length);
    ret = ret.substr(0, ret.indexOf('('));
    return ret;
}<commit_msg>Clone ticks array before appling the sort method<commit_after>'use strict';

const co = require('co');

/**
 * Contains all timers.
 * @type {{}}
 */
const timers = {};

/**
 * Timers factory object.
 * @param name
 * @returns {*}
 */
const timer = function (name) {
    if (typeof timers[name] === 'undefined') {
        timers[name] = {
            ticks: [],

            /**
             * Get the median of all ticks.
             * @returns {*}
             */
            median: function () {
                if (this.ticks.length > 1) {
                    const sorted = this.ticks.slice(0).sort(function (a, b) {
                        return a && b && (a.getDiff() - b.getDiff()) || 0;
                    });

                    const l = sorted.length;
                    const half = Math.floor(l / 2);

                    if (l % 2) {
                        return sorted[half].getDiff();
                    } else {
                        return (sorted[half - 1].getDiff() + sorted[half].getDiff()) / 2;
                    }
                } else {
                    return this.ticks[0].getDiff();
                }
            },

            /**
             * Get the average duration of all ticks.
             * @returns {number}
             */
            mean: function () {
                return this.duration() / this.ticks.length;
            },

            /**
             * Get the duration of all ticks.
             * @returns {number}
             */
            duration: function () {
                let sum = 0;
                for (let i = 0, l = this.ticks.length; i < l; i++) {
                    sum += this.ticks[i].getDiff();
                }
                return sum;
            },

            /**
             * Get the shortest tick.
             * @returns {number}
             */
            min: function () {
                let min = this.ticks[0].getDiff();
                this.ticks.forEach(function (tick) {
                    if (tick.getDiff() < min) {
                        min = tick.getDiff();
                    }
                });

                return min;
            },

            /**
             * Get the longest tick.
             * @returns {number}
             */
            max: function () {
                let max = 0;
                this.ticks.forEach(function (tick) {
                    if (tick.getDiff() > max) {
                        max = tick.getDiff();
                    }
                });

                return max;
            },

            /**
             * Get the number of ticks.
             * @returns {Number}
             */
            count: function () {
                return Object.keys(this.ticks).length;
            },

            /**
             * Parse the numbers nicely.
             * @param num
             * @returns {string}
             */
            parse: function (num) {
                if (num < 1e3) {
                    return num + 'ns';
                } else if (num >= 1e3 && num < 1e6) {
                    return num / 1e3 + 'us';
                } else if (num >= 1e6 && num < 1e9) {
                    return num / 1e6 + 'ms';
                } else if (num >= 1e9) {
                    return num / 1e9 + 's';
                }
            }
        };
    }

    return timers[name];
};

/**
 * Check if `obj` is a generator function.
 *
 * @param {Mixed} obj
 * @return {Boolean}
 * @api private
 */
function isGeneratorFunction(obj) {
    var constructor = obj.constructor;

    if (!constructor) {
        return false;
    }

    if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) {
        return true;
    }

    return 'function' === typeof constructor.prototype.next && 'function' === typeof constructor.prototype.throw;
}

/**
 * Constructor of tick.
 * @param name The name of this tick.
 * @returns {Tick}
 * @constructor
 */
function Tick(name) {
    this.name = name;
    return this;
}

Tick.wrap = function (name, callback) {
    if (typeof name === 'function') {
        callback = name;
        name = functionName(callback);
    }

    if (name === '') {
        name = 'anon';
    }

    const tick = new Tick(name);
    tick.start();

    const done = function () {
        tick.stop();
    };

    if (isGeneratorFunction(callback)) {
        co(callback).then(done, done);
    } else if(!!callback.toString().match(/^function.*\(.*\)/)) {
        // If done is passed when the callback is declared than we assume is async
        callback(done);
    } else {
        // Otherwise just call the function and stop the tick
        callback();
        tick.stop();
    }

    return tick;
};

/**
 * Starts the tick.
 */
Tick.prototype.start = function () {
    this.hrstart = process.hrtime();
};

/**
 * Ends the tick.
 */
Tick.prototype.stop = function () {
    this.hrend = process.hrtime(this.hrstart);
    timer(this.name).ticks.push(this);
};

/**
 * Get the duration of the tick.
 * @returns Long nanoseconds
 */
Tick.prototype.getDiff = function () {
    return this.hrend[0] * 1e9 + this.hrend[1];
};

module.exports = {
    timer: timer,
    timers: timers,
    Tick: Tick
};

/**
 * Helper function used to retrieve function name.
 * @param fun
 * @returns {string}
 */
function functionName(fun) {
    let ret = fun.toString();
    ret = ret.substr('function '.length);
    ret = ret.substr(0, ret.indexOf('('));
    return ret;
}<|endoftext|>


<commit_before>var kisslog = require('kisslog');

var updateHeaderMap = {
    'etag': 'if-match',
    'last-modified': 'if-unmodified-since'
};

module.exports = function (base, _config) {
    var config = _config || {};
    var log = kisslog(config);
    if (!config.type) config.type = 'etag';
    if (config.type !== 'etag' && config.type !== 'last-modified') {
        throw new Error('Allowed types are "etag" and "last-modified"');
    }
    var base_sync = (base.prototype && base.prototype.sync) ? base.prototype.sync : base.sync;
    if (!base_sync) throw new Error('optimistic-sync requires an existing sync implementation to wrap.');
    log('optimistic-sync config:', config);

    function handleOutOfSync(model, xhr) {
        log('optimistic-sync handling invalid-version');
        var version = xhr.getResponseHeader(config.type);
        var mime = xhr.getResponseHeader('content-type');
        var data;
        if (mime.indexOf('json') !== -1) {
            try {
                data = JSON.parse(xhr.responseText);
            } catch (e) {}
        }
        model.trigger('sync:invalid-version', model, version, data);
    }

    function setupOptions(method, model, options) {
        log('optimistic-sync setting up options');
        if (method === 'update' || method === 'patch') {
            var error = options.error;
            options.error = function (xhr, status, message) {
                if (xhr.status === 412) {
                    handleOutOfSync(model, xhr);
                }
                if (typeof error === 'function') error(xhr, status, message);
            };
        }
        var success = options.success;
        options.success = function (data, status, xhr) {
            var version = xhr.getResponseHeader(config.type);
            if (version) {
                model._version = version;
                if (data && typeof data === 'object') {
                    model._serverState = data;
                }
                model.trigger('sync:version', model, version);
            }
            if (success) success(data, status, xhr);
        };
        var key;
        if (config.options) {
            if (typeof config.options.all === 'object') {
                for (key in config.options.all) {
                    options[key] = config.options.all[key];
                }
            }
            if (typeof config.options[method] === 'object') {
                for (key in config.options[method]) {
                    options[key] = config.options[method][key];
                }
            }
        }
    }

    return {
        _optimisticSync: config,
        sync: function (method, model, options) {
            log('optimistic-sync sync called');
            if (!options) options = {};
            setupOptions(method, model, options);
            if ((method === 'update' || method === 'patch') && model._version) {
                if (typeof config.invalidHandler === 'function' && !this._syncInvalidListening) {
                    log('optimistic-sync listening to sync:invalid-version with configured handler');
                    this.listenTo(this, 'sync:invalid-version', config.invalidHandler);
                    this._syncInvalidListening = true;
                }
                if (!options.headers) options.headers = {};
                options.headers[updateHeaderMap[config.type]] = model._version;
            }
            return base_sync.call(this, method, model, options);
        }
    };
};

module.exports.headers = updateHeaderMap;<commit_msg>pre-apply default opts before wrapping success/error<commit_after>var kisslog = require('kisslog');

var updateHeaderMap = {
    'etag': 'if-match',
    'last-modified': 'if-unmodified-since'
};

module.exports = function (base, _config) {
    var config = _config || {};
    var log = kisslog(config);
    if (!config.type) config.type = 'etag';
    if (config.type !== 'etag' && config.type !== 'last-modified') {
        throw new Error('Allowed types are "etag" and "last-modified"');
    }
    var base_sync = (base.prototype && base.prototype.sync) ? base.prototype.sync : base.sync;
    if (!base_sync) throw new Error('optimistic-sync requires an existing sync implementation to wrap.');
    log('optimistic-sync config:', config);

    function handleOutOfSync(model, xhr) {
        log('optimistic-sync handling invalid-version');
        var version = xhr.getResponseHeader(config.type);
        var mime = xhr.getResponseHeader('content-type');
        var data;
        if (mime.indexOf('json') !== -1) {
            try {
                data = JSON.parse(xhr.responseText);
            } catch (e) {}
        }
        model.trigger('sync:invalid-version', model, version, data);
    }

    function setupOptions(method, model, options) {
        log('optimistic-sync setting up options');
        var key;
        if (config.options) {
            if (typeof config.options.all === 'object') {
                for (key in config.options.all) {
                    options[key] = config.options.all[key];
                }
            }
            if (typeof config.options[method] === 'object') {
                for (key in config.options[method]) {
                    options[key] = config.options[method][key];
                }
            }
        }
        if (method === 'update' || method === 'patch') {
            var error = options.error;
            options.error = function (xhr, status, message) {
                if (xhr.status === 412) {
                    handleOutOfSync(model, xhr);
                }
                if (typeof error === 'function') error(xhr, status, message);
            };
        }
        var success = options.success;
        options.success = function (data, status, xhr) {
            var version = xhr.getResponseHeader(config.type);
            if (version) {
                model._version = version;
                if (data && typeof data === 'object') {
                    model._serverState = data;
                }
                model.trigger('sync:version', model, version);
            }
            if (success) success(data, status, xhr);
        };
    }

    return {
        _optimisticSync: config,
        sync: function (method, model, options) {
            log('optimistic-sync sync called');
            if (!options) options = {};
            setupOptions(method, model, options);
            if ((method === 'update' || method === 'patch') && model._version) {
                if (typeof config.invalidHandler === 'function' && !this._syncInvalidListening) {
                    log('optimistic-sync listening to sync:invalid-version with configured handler');
                    this.listenTo(this, 'sync:invalid-version', config.invalidHandler);
                    this._syncInvalidListening = true;
                }
                if (!options.headers) options.headers = {};
                options.headers[updateHeaderMap[config.type]] = model._version;
            }
            return base_sync.call(this, method, model, options);
        }
    };
};

module.exports.headers = updateHeaderMap;<|endoftext|>


<commit_before>'use strict';

const readline         = require('readline');
const _                = require('lodash');
const colors           = require('./colors');
const utils            = require('util');
const spinners         = require('cli-spinners')
BrailleLoader.spinners = spinners;

var _defaultOptions = {
	color: 'white',
	frames: null,
	currentFrame: 0,
	speed: 80,
	spinner: 'dots',
	spinnerId: 0,
	text: ''
};

function BrailleLoader(options){
	if (typeof options === 'string'){
		this.options = _.extend({}, _defaultOptions, { 'text': options });
		this.options.frames = this.getSpinner(_defaultOptions.spinner).frames;
		this.options.speed = this.getSpinner(_defaultOptions.spinner).interval;
	} else {
		this.options = _.extend({}, _defaultOptions, options);
		this.options.frames = this.getSpinner(this.options.spinner).frames;
		this.options.speed = this.getSpinner(this.options.spinner).interval;
	}
}

BrailleLoader.prototype.start = function(){
	var self = this;
	process.stdout.write(self.options.frames[self.options.currentFrame]);
  process.stdout.write('\x1b[?25l');// blink cursor off
	self.options.spinnerId = setInterval(function(){
		self.paintFrames()
	},self.options.speed);
}

BrailleLoader.prototype.stop = function(){
	this.destroy();
}

BrailleLoader.prototype.destroy = function(){
	clearInterval(this.options.spinnerId);
	resetPrompt();
}

BrailleLoader.prototype.getColor = function(){
	return colors[this.options.color.toLowerCase()] || '\x1b[37m';
}

BrailleLoader.prototype.setColor = function(color){
	this.color = color;
}

BrailleLoader.prototype.paintFrames = function(){
	this.clear(); // clear previous output
	var colorCode = this.getColor();
	this.options.currentFrame++;
	if(this.options.currentFrame >= this.options.frames.length){
		this.options.currentFrame	 = 0;
	}
	readline.moveCursor(process.stdout, -this.options.frames[this.options.currentFrame].length , 0);
	var str = `${this.getColor()}${this.options.frames[this.options.currentFrame]} ${this.options.text}`

	process.stdout.write('\r' + str);
}

BrailleLoader.prototype.getSpinner = function(spinner){
	if (!spinners[spinner]){
		console.warn('Invalid spinner type refer cli-spinners for all types');
		process.exit(0);
	}
	return spinners[spinner];
}

BrailleLoader.prototype.clear = function(){
	process.stdout.clearLine();
}

BrailleLoader.prototype.setText = function(str){
	this.options.text = str;
}

function resetPrompt(){
	process.stdout.write('\x1B[?25h')//show's the cursor
	process.stdout.write('\x1B[0m')
	process.exit(0);
}

process.on('SIGINT', resetPrompt);

module.exports = BrailleLoader;

var l = new BrailleLoader({
	spinner: 'moon',
	color: 'green',
	text: 'Loading'
})

l.start();

setTimeout(function(){
	l.setText("watching moons");
	setTimeout(function(){
		console.log('stopping')
		l.stop()
	},5000)
},5000)
<commit_msg>removed console<commit_after>'use strict';

const readline         = require('readline');
const _                = require('lodash');
const colors           = require('./colors');
const utils            = require('util');
const spinners         = require('cli-spinners')
BrailleLoader.spinners = spinners;

var _defaultOptions = {
	color: 'white',
	frames: null,
	currentFrame: 0,
	speed: 80,
	spinner: 'dots',
	spinnerId: 0,
	text: ''
};

function BrailleLoader(options){
	if (typeof options === 'string'){
		this.options = _.extend({}, _defaultOptions, { 'text': options });
		this.options.frames = this.getSpinner(_defaultOptions.spinner).frames;
		this.options.speed = this.getSpinner(_defaultOptions.spinner).interval;
	} else {
		this.options = _.extend({}, _defaultOptions, options);
		this.options.frames = this.getSpinner(this.options.spinner).frames;
		this.options.speed = this.getSpinner(this.options.spinner).interval;
	}
}

BrailleLoader.prototype.start = function(){
	var self = this;
	process.stdout.write(self.options.frames[self.options.currentFrame]);
  process.stdout.write('\x1b[?25l');// blink cursor off
	self.options.spinnerId = setInterval(function(){
		self.paintFrames()
	},self.options.speed);
}

BrailleLoader.prototype.stop = function(){
	this.destroy();
}

BrailleLoader.prototype.destroy = function(){
	clearInterval(this.options.spinnerId);
	resetPrompt();
}

BrailleLoader.prototype.getColor = function(){
	return colors[this.options.color.toLowerCase()] || '\x1b[37m';
}

BrailleLoader.prototype.setColor = function(color){
	this.color = color;
}

BrailleLoader.prototype.paintFrames = function(){
	this.clear(); // clear previous output
	var colorCode = this.getColor();
	this.options.currentFrame++;
	if(this.options.currentFrame >= this.options.frames.length){
		this.options.currentFrame	 = 0;
	}
	readline.moveCursor(process.stdout, -this.options.frames[this.options.currentFrame].length , 0);
	var str = `${this.getColor()}${this.options.frames[this.options.currentFrame]} ${this.options.text}`

	process.stdout.write('\r' + str);
}

BrailleLoader.prototype.getSpinner = function(spinner){
	if (!spinners[spinner]){
		console.warn('Invalid spinner type refer cli-spinners for all types');
		process.exit(0);
	}
	return spinners[spinner];
}

BrailleLoader.prototype.clear = function(){
	process.stdout.clearLine();
}

BrailleLoader.prototype.setText = function(str){
	this.options.text = str;
}

function resetPrompt(){
	process.stdout.write('\x1B[?25h')//show's the cursor
	process.stdout.write('\x1B[0m')
	process.exit(0);
}

process.on('SIGINT', resetPrompt);

module.exports = BrailleLoader;
<|endoftext|>


<commit_before>/*!
 * use <https://github.com/jonschlinkert/use>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var utils = require('./utils')

module.exports = function base(app, opts) {
  if (!utils.isObject(app) && typeof app !== 'function') {
    throw new TypeError('use: expect `app` be an object or function');
  }

  opts = utils.isObject(opts) ? opts : {};
  opts.prop = typeof opts.prop === 'string' ? opts.prop : 'fns';
  opts.prop = opts.prop.length > 0 ? opts.prop : 'fns';

  if (!utils.isArray(app[opts.prop])) {
    utils.define(app, opts.prop, []);
  }

  /**
   * Define a plugin function to be passed to use. The only
   * parameter exposed to the plugin is `app`, the object or function.
   * passed to `use(app)`. `app` is also exposed as `this` in plugins.
   *
   * Additionally, **if a plugin returns a function, the function will
   * be pushed onto the `fns` array**, allowing the plugin to be
   * called at a later point by the `run` method.
   *
   * ```js
   * var use = require('use');
   *
   * // define a plugin
   * function foo(app) {
   *   // do stuff
   * }
   *
   * var app = function(){};
   * use(app);
   *
   * // register plugins
   * app.use(foo);
   * app.use(bar);
   * app.use(baz);
   * ```
   * @name .use
   * @param {Function} `fn` plugin function to call
   * @api public
   */

  utils.define(app, 'use', use);

  /**
   * Run all plugins on `fns`. Any plugin that returns a function
   * when called by `use` is pushed onto the `fns` array.
   *
   * ```js
   * var config = {};
   * app.run(config);
   * ```
   * @name .run
   * @param {Object} `value` Object to be modified by plugins.
   * @return {Object} Returns the object passed to `run`
   * @api public
   */

  utils.define(app, 'run', function (val) {
    var self = this || app;
    var fns = self[opts.prop];
    decorate(val);
    var len = fns.length, i = -1;
    while (++i < len) val.use(fns[i]);
    return val;
  });

  /**
   * Call plugin `fn`. If a function is returned push it into the
   * `fns` array to be called by the `run` method.
   */

  function use(fn, options) {
    if (typeof fn !== 'function') {
      throw new TypeError('.use expect `fn` be function');
    }
    var self = this || app;

    if (typeof opts.fn === 'function') {
      opts.fn.call(self, self, options);
    }

    var plugin = fn.call(self, self);
    if (typeof plugin === 'function') {
      var fns = self[opts.prop];
      fns.push(plugin);
    }
    return self;
  }

  /**
   * Ensure the `.use` method exists on `val`
   */

  function decorate(val) {
    if (utils.isObject(val) && (!val.use || !val.run)) {
      base(val);
    }
  }

  return app;
};
<commit_msg>minor formatting, ensure `val` is an object<commit_after>/*!
 * use <https://github.com/jonschlinkert/use>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

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

module.exports = function base(app, opts) {
  if (!utils.isObject(app) && typeof app !== 'function') {
    throw new TypeError('use: expect `app` be an object or function');
  }

  if (!utils.isObject(opts)) {
    opts = {};
  }

  var prop = utils.isString(opts.prop) ? opts.prop : 'fns';
  if (!utils.isArray(app[prop])) {
    utils.define(app, prop, []);
  }

  /**
   * Define a plugin function to be passed to use. The only
   * parameter exposed to the plugin is `app`, the object or function.
   * passed to `use(app)`. `app` is also exposed as `this` in plugins.
   *
   * Additionally, **if a plugin returns a function, the function will
   * be pushed onto the `fns` array**, allowing the plugin to be
   * called at a later point by the `run` method.
   *
   * ```js
   * var use = require('use');
   *
   * // define a plugin
   * function foo(app) {
   *   // do stuff
   * }
   *
   * var app = function(){};
   * use(app);
   *
   * // register plugins
   * app.use(foo);
   * app.use(bar);
   * app.use(baz);
   * ```
   * @name .use
   * @param {Function} `fn` plugin function to call
   * @api public
   */

  utils.define(app, 'use', use);

  /**
   * Run all plugins on `fns`. Any plugin that returns a function
   * when called by `use` is pushed onto the `fns` array.
   *
   * ```js
   * var config = {};
   * app.run(config);
   * ```
   * @name .run
   * @param {Object} `value` Object to be modified by plugins.
   * @return {Object} Returns the object passed to `run`
   * @api public
   */

  utils.define(app, 'run', function(val) {
    if (!utils.isObject(val)) return;
    decorate(val);

    var self = this || app;
    var fns = self[prop];
    var len = fns.length;
    var idx = -1;

    while (++idx < len) {
      val.use(fns[idx]);
    }
    return val;
  });

  /**
   * Call plugin `fn`. If a function is returned push it into the
   * `fns` array to be called by the `run` method.
   */

  function use(fn, options) {
    if (typeof fn !== 'function') {
      throw new TypeError('.use expects `fn` be a function');
    }

    var self = this || app;
    if (typeof opts.fn === 'function') {
      opts.fn.call(self, self, options);
    }

    var plugin = fn.call(self, self);
    if (typeof plugin === 'function') {
      var fns = self[prop];
      fns.push(plugin);
    }
    return self;
  }

  /**
   * Ensure the `.use` method exists on `val`
   */

  function decorate(val) {
    if (!val.use || !val.run) {
      base(val);
    }
  }

  return app;
};
<|endoftext|>


<commit_before>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.returnExports = factory();
  }
}(this, function() {
  'use strict';

  var Interface = {};

  Interface.CACHE = {
    CONTEXT: {}
  };

  // Additional types

  Interface.StringOrArray = function (val) {
    return val.constructor === String || val.constructor === Array;
  };

  Interface.NumberOrArray = function (val) {
    return val.constructor === Number || val.constructor === Array;
  };

  Interface.Promise = function (val) {
    return val.always && val.then;
  };

  Interface.optional = function (type) {
    var f = function(val) {
      if (!val) return true;

      if (typeof type === 'function' && typeof val === 'function') {
        return true;
      } else if (val.constructor == type || (val instanceof type)) {
        return true;
      }
      return false;
    };

    f.Optional = true;

    return f;
  };

  // Private

  var isMatchingArgument = function (argVal, argType) {
    if (argType === undefined && argVal === undefined) {
      return true;
    }

    var isNativeType = [
        'object',
        'boolean',
        'number',
        'string',
        'function'
      ].some(function(t) { return t === typeof argType; });

    var wantArray = false;
    if (isNativeType)
      wantArray = (argType.toString().indexOf('Array') > -1);

    if (argType.Optional && argType(argVal)) {
      return true;
    } else if (!isNativeType && argType(argVal)) {
      return true;
    } else if (isNativeType) {
      // objects are not arrays!
      if (wantArray && argVal instanceof Array) {
        return true;
      } else if (!wantArray &&
        (argType.toString().toLowerCase().indexOf(typeof argVal) > -1)) {
        return true;
      }
    }

    return false;
  };

  /**
   * Wraps a function for defineFunction or defineMethod.
   *
   * Type checks all arguments and the return value of the original Function.
   * If everything passes, then return the original Functions return value.
   */
  var wrapper = function (argVals, argTypes, rType, func, name) {
    var i, l;
    var givenLen = argVals.length;
    var expectedLen = 0;
    var optionalLen = 0;

    argTypes.forEach(function(t) {
      if (t.Optional) optionalLen += 1;
      else expectedLen += 1;
    });

    if ((optionalLen > 0 && givenLen !== (optionalLen + expectedLen)) ||
       (optionalLen === 0 && givenLen !== expectedLen)) {
      throw new TypeError("Mismatched argument lengths: got " + givenLen + ", wanted " + expectedLen);
    }

    // iterate each given arg, testing if valid type
    for (i = 0, l = givenLen; i < l; i++) {
      var argVal = argVals[i];
      var argType = argTypes[i];

      if (isMatchingArgument(argVal, argType))
        continue;

      throw new TypeError("Mismatched argument " + (i+1) + ": got " + typeof argVal + ", wanted " + argType + " for " + name + "()");
    }

    // return the valid return value of the original function or throw
    var rVal = func.apply(this, argVals);
    if (isMatchingArgument(rVal, rType))
      return rVal;

    throw new TypeError("Mismatched return value (got: " + typeof (rVal) + ", wanted: " + rType + ")");
  };

  // Public

  /**
   * Define types on an Object's property.
   *
   * @param  {Object} ctx      Reference to some Object
   * @param  {String} name     Property name of a Function
   * @param  {Array}  argTypes Array of types ex. [Number, Boolean, ..]
   * @param  {Mixed}  rType    The return value's type ex. Boolean
   */
  Interface.defineFunction = function (ctx, name, argTypes, rType) {
    var func = ctx[name];

    if (!func || !(func instanceof Function)) {
      throw new TypeError("Function: " + name + " in context: " + ctx + " isn't a valid function: " + func);
    }

    var wrapped = function () {
      return wrapper.apply(this, [arguments, argTypes, rType, func]);
    };

    ctx[name] = wrapped;

    var cacheObj = {
      ctx: ctx,
      original: func,
      wrapped: wrapped
    };

    Interface.CACHE.CONTEXT[ctx] = Interface.CACHE.CONTEXT[ctx] || {};
    Interface.CACHE.CONTEXT[ctx][name] = cacheObj;
  };

  /**
   * Define types on an instance's property.
   *
   * @param  {Prototype} cls      An Object instance
   * @param  {String}    name     Property name of a method
   * @param  {Array}     argTypes Array of types ex. [Number, Boolean, ..]
   * @param  {Mixed}     rType    The return value's type ex. Boolean
   */
  Interface.defineMethod = function (cls, name, argTypes, rType) {
    var defMethod = function (ctxs, c, name, argTypes, rType) {
      var ctx = ctxs[c];
      var func = ctx.prototype[name];

      if (!func || !(func instanceof Function)) {
        throw new TypeError("Function: " + name + " in context: " + ctxs + " isn't a valid function: " + func);
      }

      var wrapped = function () {
        return wrapper.apply(this, [arguments, argTypes, rType, func, name]);
      };

      ctx.prototype[name] = wrapped;

      var cacheObj = {
        ctx: ctx,
        original: func,
        wrapped: wrapped,
        args: Array.prototype.slice.call(arguments, -2),
        test: true
      };

      Interface.CACHE.CONTEXT[ctx] = Interface.CACHE.CONTEXT[ctx] || {};
      Interface.CACHE.CONTEXT[ctx][name] = cacheObj;
    };

    var ctxs = [];
    ctxs.push(cls);

    for (var c = 0, l = ctxs.length; c < l; c++) {
      defMethod(ctxs, c, name, argTypes, rType);
    }
  };

  /**
   * Adopt the 'type rules' of cached Prototype.
   *
   * @param  {Prototype} parent
   * @param  {Prototype} child
   * @return {Reference}
   */
  Interface.extend = function(ctx, child) {
    // apply wrapper to child properties
    var cached = Interface.CACHE.CONTEXT[ctx];
    for (var prop in cached) {
      var args = cached[prop].args;

      // adopt missing methods from ctx
      if (!child.prototype.hasOwnProperty(prop)) {
        child.prototype[prop] = cached[prop].original;
      }

      Interface.defineMethod(child, prop, args[0], args[1]);
    }

    return child;
  };

  // var MyClass = function() {};
  // MyClass.prototype.test = function(a,b,c) { return true; };
  // Interface.defineMethod(MyClass, 'test', [Number, Array, Array], Boolean);
  // var testClass = new MyClass();
  // var r = testClass.test(1, {}, [1,2,3]);

  return Interface;
}));
<commit_msg>Add StringOrObject type.<commit_after>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.returnExports = factory();
  }
}(this, function() {
  'use strict';

  var Interface = {};

  Interface.CACHE = {
    CONTEXT: {}
  };

  // Additional types

  Interface.StringOrArray = function (val) {
    return val.constructor === String || val.constructor === Array;
  };

  Interface.StringOrObject = function (val) {
    return val.constructor === String || val.constructor === Object;
  };

  Interface.NumberOrArray = function (val) {
    return val.constructor === Number || val.constructor === Array;
  };

  Interface.Promise = function (val) {
    return val.always && val.then;
  };

  Interface.optional = function (type) {
    var f = function(val) {
      if (!val) return true;

      if (typeof type === 'function' && typeof val === 'function') {
        return true;
      } else if (val.constructor == type || (val instanceof type)) {
        return true;
      }
      return false;
    };

    f.Optional = true;

    return f;
  };

  // Private

  var isMatchingArgument = function (argVal, argType) {
    if (argType === undefined && argVal === undefined) {
      return true;
    }

    var isNativeType = [
        'object',
        'boolean',
        'number',
        'string',
        'function'
      ].some(function(t) { return t === typeof argType; });

    var wantArray = false;
    if (isNativeType)
      wantArray = (argType.toString().indexOf('Array') > -1);

    if (argType.Optional && argType(argVal)) {
      return true;
    } else if (!isNativeType && argType(argVal)) {
      return true;
    } else if (isNativeType) {
      // objects are not arrays!
      if (wantArray && argVal instanceof Array) {
        return true;
      } else if (!wantArray &&
        (argType.toString().toLowerCase().indexOf(typeof argVal) > -1)) {
        return true;
      }
    }

    return false;
  };

  /**
   * Wraps a function for defineFunction or defineMethod.
   *
   * Type checks all arguments and the return value of the original Function.
   * If everything passes, then return the original Functions return value.
   */
  var wrapper = function (argVals, argTypes, rType, func, name) {
    var i, l;
    var givenLen = argVals.length;
    var expectedLen = 0;
    var optionalLen = 0;

    argTypes.forEach(function(t) {
      if (t.Optional) optionalLen += 1;
      else expectedLen += 1;
    });

    if ((optionalLen > 0 && givenLen !== (optionalLen + expectedLen)) ||
       (optionalLen === 0 && givenLen !== expectedLen)) {
      throw new TypeError("Mismatched argument lengths: got " + givenLen + ", wanted " + expectedLen);
    }

    // iterate each given arg, testing if valid type
    for (i = 0, l = givenLen; i < l; i++) {
      var argVal = argVals[i];
      var argType = argTypes[i];

      if (isMatchingArgument(argVal, argType))
        continue;

      throw new TypeError("Mismatched argument " + (i+1) + ": got " + typeof argVal + ", wanted " + argType + " for " + name + "()");
    }

    // return the valid return value of the original function or throw
    var rVal = func.apply(this, argVals);
    if (isMatchingArgument(rVal, rType))
      return rVal;

    throw new TypeError("Mismatched return value (got: " + typeof (rVal) + ", wanted: " + rType + ")");
  };

  // Public

  /**
   * Define types on an Object's property.
   *
   * @param  {Object} ctx      Reference to some Object
   * @param  {String} name     Property name of a Function
   * @param  {Array}  argTypes Array of types ex. [Number, Boolean, ..]
   * @param  {Mixed}  rType    The return value's type ex. Boolean
   */
  Interface.defineFunction = function (ctx, name, argTypes, rType) {
    var func = ctx[name];

    if (!func || !(func instanceof Function)) {
      throw new TypeError("Function: " + name + " in context: " + ctx + " isn't a valid function: " + func);
    }

    var wrapped = function () {
      return wrapper.apply(this, [arguments, argTypes, rType, func]);
    };

    ctx[name] = wrapped;

    var cacheObj = {
      ctx: ctx,
      original: func,
      wrapped: wrapped
    };

    Interface.CACHE.CONTEXT[ctx] = Interface.CACHE.CONTEXT[ctx] || {};
    Interface.CACHE.CONTEXT[ctx][name] = cacheObj;
  };

  /**
   * Define types on an instance's property.
   *
   * @param  {Prototype} cls      An Object instance
   * @param  {String}    name     Property name of a method
   * @param  {Array}     argTypes Array of types ex. [Number, Boolean, ..]
   * @param  {Mixed}     rType    The return value's type ex. Boolean
   */
  Interface.defineMethod = function (cls, name, argTypes, rType) {
    var defMethod = function (ctxs, c, name, argTypes, rType) {
      var ctx = ctxs[c];
      var func = ctx.prototype[name];

      if (!func || !(func instanceof Function)) {
        throw new TypeError("Function: " + name + " in context: " + ctxs + " isn't a valid function: " + func);
      }

      var wrapped = function () {
        return wrapper.apply(this, [arguments, argTypes, rType, func, name]);
      };

      ctx.prototype[name] = wrapped;

      var cacheObj = {
        ctx: ctx,
        original: func,
        wrapped: wrapped,
        args: Array.prototype.slice.call(arguments, -2),
        test: true
      };

      Interface.CACHE.CONTEXT[ctx] = Interface.CACHE.CONTEXT[ctx] || {};
      Interface.CACHE.CONTEXT[ctx][name] = cacheObj;
    };

    var ctxs = [];
    ctxs.push(cls);

    for (var c = 0, l = ctxs.length; c < l; c++) {
      defMethod(ctxs, c, name, argTypes, rType);
    }
  };

  /**
   * Adopt the 'type rules' of cached Prototype.
   *
   * @param  {Prototype} parent
   * @param  {Prototype} child
   * @return {Reference}
   */
  Interface.extend = function(ctx, child) {
    // apply wrapper to child properties
    var cached = Interface.CACHE.CONTEXT[ctx];
    for (var prop in cached) {
      var args = cached[prop].args;

      // adopt missing methods from ctx
      if (!child.prototype.hasOwnProperty(prop)) {
        child.prototype[prop] = cached[prop].original;
      }

      Interface.defineMethod(child, prop, args[0], args[1]);
    }

    return child;
  };

  // var MyClass = function() {};
  // MyClass.prototype.test = function(a,b,c) { return true; };
  // Interface.defineMethod(MyClass, 'test', [Number, Array, Array], Boolean);
  // var testClass = new MyClass();
  // var r = testClass.test(1, {}, [1,2,3]);

  return Interface;
}));
<|endoftext|>