<NME> sysend.js
<BEF> /**@license
 *  sysend.js - send messages between browser windows/tabs version 1.12.1
 *
 *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>
 *  Released under the MIT license
 *
 *  The idea for localStorage implementation came from this StackOverflow question:
 *  http://stackoverflow.com/q/24182409/387194
 *
 */
/* global define, module, exports, localStorage, setTimeout */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['sysend'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.sysend = factory();
    }
})(typeof window !== "undefined" ? window : this, function() {
    var collecting_timeout = 400;
    // we use prefix so `foo' event don't collide with `foo' locaStorage value
    var uniq_prefix = '___sysend___';
    var prefix_re = new RegExp(uniq_prefix);
    var random_value = Math.random();
    var serializer = {};
    // object with user events as keys and values arrays of callback functions
    var callbacks = {};
    var has_primary;
    var iframes = [];
    var index = 0;
    var proxy_mode = false;
    var channel;
    var primary = true;
    var force_ls = false;
    // we use id because storage event is not executed if message was not
    // changed, and we want it if user send same object twice (before it will
    // be removed)
    var id = 0;
    // identifier for making each call to list unique
    var list_id = 0;

    // id of the window/tabAnd two-way communication is tracked in 
    var target_id = generate_uuid();
    var target_count = 1;
    var domains;

    var handlers = {
        primary: [],
        close: [],
        open: [],
        secondary: [],
        message: [],
        visbility: [],
        ready: []
    };
    var events = Object.keys(handlers);
    // -------------------------------------------------------------------------
    var serialize = make_process(serializer, 'to');
    var unserialize = make_process(serializer, 'from');
    // -------------------------------------------------------------------------
    var sysend = {
        id: target_id,
        broadcast: function(event, data) {
            if (channel && !force_ls) {
                channel.postMessage({name: event, data: serialize(data)});
            } else {
                set(event, to_json(data));
                // clean up localstorage
                setTimeout(function() {
                    remove(event);
                }, 0);
            }
            send_to_iframes(event, data);
            return sysend;
        },
        emit: function(event, data) {
            sysend.broadcast(event, data);
            invoke(event, data);
            return sysend;
        },
        serializer: function(to, from) {
            if (typeof to !== 'function' || typeof from !== 'function') {
                throw new Error('sysend::serializer: Invalid argument, expecting' +
                                ' function');
            }
            serializer.to = to;
            serializer.from = from;
            return sysend;
        },
        proxy: function() {
            [].slice.call(arguments).forEach(function(url) {
                if (typeof url === 'string' && host(url) !== window.location.host) {
                    domains = domains || [];
                    domains.push(origin(url));
                    var iframe = document.createElement('iframe');
                    iframe.style.width = iframe.style.height = 0;
                    iframe.style.border = 'none';
                    var proxy_url = url;
                    if (!url.match(/\.html$/)) {
                        proxy_url = url.replace(/\/$/, '') + '/proxy.html';
                    }
                    iframe.addEventListener('error', function handler() {
                        setTimeout(function() {
                            throw new Error('html proxy file not found on "' + url +
                                            '" url');
                        }, 0);
                        iframe.removeEventListener('error', handler);
                    });
                    iframe.addEventListener('load', function handler() {
                        var win;
                        // fix for Safari
                        // https://stackoverflow.com/q/42632188/387194
                        try {
                            win = iframe.contentWindow;
                        } catch(e) {
                            win = iframe.contentWindow;
                        }
                        iframes.push({window: win, node: iframe});
                        iframe.removeEventListener('load', handler);
                    });
                    document.body.appendChild(iframe);
                    iframe.src = proxy_url;
                }
            });
            if (!arguments.length && is_iframe) {
                proxy_mode = true;
            }
            return sysend;
        },
        on: function(event, fn) {
            if (!callbacks[event]) {
                callbacks[event] = [];
            }
            callbacks[event].push(fn);
            return sysend;
        },
        off: function(event, fn) {
            if (callbacks[event]) {
                if (fn) {
                    for (var i = callbacks[event].length; i--;) {
                        if (callbacks[event][i] == fn) {
                            callbacks[event].splice(i, 1);
                        }
                    }
                } else {
                    callbacks[event] = [];
                }
            }
            return sysend;
        },
        track: function(event, fn) {
            if (events.includes(event)) {
                handlers[event].push(fn);
            }
            return sysend;
        },
        untrack: function(event, fn) {
            if (events.includes(event) && handlers[event].length) {
                if (fn === undefined) {
                    handlers[event] = [];
                } else {
                    handlers[event] = handlers[event].filter(function(handler) {
                        return handler !== fn;
                    });
                }
            }
            return sysend;
        },
        post: function(target, data) {
            return sysend.broadcast('__message__', {
                target: target,
                data: data,
                origin: target_id
            });
        },
        list: function() {
            var id = list_id++;
            var marker = { target: target_id, id: id };
            var timer = delay(sysend.timeout);
            return new Promise(function(resolve) {
                var ids = [];
                sysend.on('__window_ack__', function(data) {
                    if (data.origin.target === target_id && data.origin.id === id) {
                        ids.push({
                            id: data.id,
                            primary: data.primary
                        });
                    }
                });
                sysend.broadcast('__window__', { id: marker });
                timer().then(function() {
                    resolve(ids);
                });
            });
        },
        channel: function() {
            domains = [].slice.apply(arguments).map(origin);
            return sysend;
        },
        isPrimary: function() {
            return primary;
        },
        useLocalStorage: function(toggle) {
            if (typeof toggle === 'boolean') {
                force_ls = toggle;
            } else {
                force_ls = true;
            }
        },
    };
    // -------------------------------------------------------------------------
    Object.defineProperty(sysend, 'timeout', {
        enumerable: true,
        get: function() {
            return collecting_timeout;
        },
        set: function(value) {
            if (typeof value === 'number' && !isNaN(value)) {
                collecting_timeout = value;
            }
        }
    });
    // -------------------------------------------------------------------------
    var host = (function() {
        if (typeof URL !== 'undefined') {
            return function(url) {
                url = new URL(url);
                return url.host;
            };
        }
        var a = document.createElement('a');
        return function(url) {
            a.href = url;
            return a.host;
        };
    })();
    // -------------------------------------------------------------------------
    function delay(time) {
        return function() {
            return new Promise(function(resolve) {
                setTimeout(resolve, time);
            });
        };
    }
    // -------------------------------------------------------------------------
    var origin = (function() {
        if (window.URL) {
            return function(url) {
                return new URL(url).origin;
            };
        }
        var a = document.createElement('a');
        return function origin(url) {
            a.href = url;
            return a.origin;
        };
    })();
    // -------------------------------------------------------------------------
    // :: show only single message of this kind
    // -------------------------------------------------------------------------
    var warn_messages = [];
    function warn(message) {
        if (!warn_messages.includes(message)) {
            warn_messages.push(message);
            if (console && console.warn) {
                console.warn(message);
            } else {
                setTimeout(function() {
                    throw new Error(message);
                }, 0);
            }
        }
    }
    // -------------------------------------------------------------------------
    function iframe_loaded() {
        var iframes = Array.from(document.querySelectorAll('iframe'));
        return Promise.all(iframes.filter(function(iframe) {
            return iframe.src;
        }).map(function(iframe) {
            return new Promise(function(resolve, reject) {
                iframe.addEventListener('load', resolve, true);
                iframe.addEventListener('error', reject, true);
            });
        })).then(delay(sysend.timeout));
        // delay is required, something with browser, it's not intialized
        // properly. The number was picked by experimentation
    }
    // -------------------------------------------------------------------------
    // :: valid sysend message
    // -------------------------------------------------------------------------
    function is_sysend_post_message(e) {
        return typeof e.data === 'string' && e.data.match(prefix_re);
    }
    // -------------------------------------------------------------------------
    function is_valid_origin(origin) {
        if (!domains) {
            warn('Call sysend.channel() on iframe to restrict domains that can '+
                 'use sysend channel');
            return true;
        }
        var valid = domains.includes(origin);
        if (!valid) {
            warn(origin + ' domain is not on the list of allowed '+
                 'domains use sysend.channel() on iframe to allow'+
                 ' access to this domain');
        }
        return valid;
    }
    // -------------------------------------------------------------------------
    // :: ref: https://stackoverflow.com/a/8809472/387194
    // :: license: Public Domain/MIT
    // -------------------------------------------------------------------------
    function generate_uuid() {
        var d = new Date().getTime();
        //Time in microseconds since page-load or 0 if unsupported
        var d2 = (performance && performance.now && (performance.now() * 1000)) || 0;
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16;
            if (d > 0) { // Use timestamp until depleted
                r = (d + r)%16 | 0;
                d = Math.floor(d/16);
            } else { // Use microseconds since page-load if supported
                r = (d2 + r)%16 | 0;
                d2 = Math.floor(d2/16);
            }
            return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
        });
    }
    // -------------------------------------------------------------------------
    function trigger(arr) {
        var args = [].slice.call(arguments, 1);
        arr.forEach(function(fn) {
            fn.apply(null, args);
        });
    }
    // -------------------------------------------------------------------------
    function get(key) {
        return localStorage.getItem(uniq_prefix + key);
    }
    // -------------------------------------------------------------------------
    function set(key, value) {
        // storage event is not fired when value is set first time
        if (id == 0) {
            localStorage.setItem(uniq_prefix + key, random_value);
        }
        localStorage.setItem(uniq_prefix + key, value);
    }
    // -------------------------------------------------------------------------
    function remove(key) {
        localStorage.removeItem(uniq_prefix + key);
    }
    // -------------------------------------------------------------------------
    function make_process(object, prop) {
        var labels = {
            from: 'Unserialize',
            to: 'Serialize'
        };
        var prefix_message = labels[prop] + ' Error: ';
        return function(data) {
            var fn = object[prop];
            try {
                if (fn) {
                    return fn(data);
                }
                return data;
            } catch (e) {
                warn(prefix_message + e.message);
            }
        };
    }
    // -------------------------------------------------------------------------
    // ref: https://stackoverflow.com/a/326076/387194
    // -------------------------------------------------------------------------
    var is_iframe = (function is_iframe() {
        try {
            return window.self !== window.top;
        } catch (e) {
            return true;
        }
    })();
    // -------------------------------------------------------------------------
    var is_private_mode = (function is_private_mode() {
        try {
            localStorage.setItem(uniq_prefix, 1);
            localStorage.removeItem(uniq_prefix);
            return false;
        } catch (e) {
            return true;
        }
    })();
    // -------------------------------------------------------------------------
    function is_proxy_iframe() {
        return is_iframe && proxy_mode;
    }
    // -------------------------------------------------------------------------
    function send_to_iframes(key, data) {
        // propagate events to iframes
        iframes.forEach(function(iframe) {
            var payload = {
              name: uniq_prefix,
              key: key,
              data: data
            };
            if (is_valid_origin(origin(iframe.node.src))) {
                iframe.window.postMessage(JSON.stringify(payload), "*");
            }
        });
    }
    // -------------------------------------------------------------------------
    function to_json(input) {
        // save random_value in storage to fix issue in IE that storage event
        // is fired on same page where setItem was called
        var obj = [id++, random_value];
        // undefined in array get stringified as null
        if (typeof input != 'undefined') {
            obj.push(input);
        }
        var data = serialize(obj);
        if (data === obj) {
            return JSON.stringify(obj);
        }
        return data;
    }
    // -------------------------------------------------------------------------
    function from_json(json) {
        var result = unserialize(json);
        if (result === json) {
            return JSON.parse(json);
        }
        return result;
    }
    // -------------------------------------------------------------------------
    function invoke(key, data) {
        callbacks[key].forEach(function(fn) {
            fn(data, key);
        });
    }
    // -------------------------------------------------------------------------
    function init_visiblity() {
        // ref: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
        var hidden, visibilityChange;
        if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
            hidden = "hidden";
            visibilityChange = "visibilitychange";
        } else if (typeof document.msHidden !== "undefined") {
            hidden = "msHidden";
            visibilityChange = "msvisibilitychange";
        } else if (typeof document.webkitHidden !== "undefined") {
            hidden = "webkitHidden";
            visibilityChange = "webkitvisibilitychange";
        }
        if (typeof document.addEventListener === 'function' && hidden) {
            document.addEventListener(visibilityChange, function() {
                trigger(handlers.visbility, !document[hidden]);
            }, false);
        }
    }
    // -------------------------------------------------------------------------
    function become_primary() {
        primary = true;
        trigger(handlers.primary);
        sysend.emit('__primary__');
    }
    // -------------------------------------------------------------------------
    function document_ready() {
        return ['interactive', 'complete'].indexOf(document.readyState) !== -1;
    }
    // -------------------------------------------------------------------------
    if (document_ready()) {
        init();
    } else {
        window.addEventListener('load', function() {
            setTimeout(init, 0);
        });
    }
    function setup_ls() {
        // we need to clean up localStorage if broadcast called on unload
        // because setTimeout will never fire, even setTimeout 0
        var re = new RegExp('^' + uniq_prefix);
        for(var key in localStorage) {
            if (key.match(re)) {
                localStorage.removeItem(key);
            }
        }
        window.addEventListener('storage', function(e) {
            // prevent event to be executed on remove in IE
            if (e.key.match(re) && index++ % 2 === 0) {
                var key = e.key.replace(re, '');
                if (callbacks[key]) {
                    var value = e.newValue || get(key);
                    if (value && value != random_value) {
                        var obj = from_json(value);
                        // don't call on remove
                        if (obj && obj[1] != random_value) {
                            invoke(key, obj[2]);
                        }
                    }
                }
            }
        }, false);
    }
    // -------------------------------------------------------------------------
    function init() {
        if (typeof window.BroadcastChannel === 'function') {
            channel = new window.BroadcastChannel(uniq_prefix);
            channel.addEventListener('message', function(event) {
                if (event.target.name === uniq_prefix) {
                    if (is_proxy_iframe()) {
                        var payload = {
                          name: uniq_prefix,
                          data: event.data,
                          iframe_id: target_id
                        };
                        if (is_valid_origin(origin(document.referrer))) {
                            window.parent.postMessage(JSON.stringify(payload), "*");
                        }
                    } else {
                        var key = event.data && event.data.name;
                        if (callbacks[key]) {
                            invoke(key, unserialize(event.data.data));
                        }
                    }
                }
            });
        } else if (is_private_mode) {
            warn('Your browser don\'t support localStorgage. ' +
                 'In Safari this is most of the time because ' +
                 'of "Private Browsing Mode"');
        }
        if (!is_private_mode) {
            setup_ls();
        }

        if (is_proxy_iframe()) {
            window.addEventListener('message', function(e) {
                if (is_sysend_post_message(e) && is_valid_origin(e.origin)) {
                    try {
                        var payload = JSON.parse(e.data);
                        if (payload && payload.name === uniq_prefix) {
                            var data = unserialize(payload.data);
                            sysend.broadcast(payload.key, data);
                        }
                    } catch(e) {
                        // ignore wrong JSON, the message don't came from Sysend
                        // even that the string have unix_prefix, this is just in case
                    }
                }
            });
        } else {
            init_visiblity();

            sysend.track('visbility', function(visible) {
                if (visible && !has_primary) {
                    become_primary();
                }
            });

            sysend.on('__primary__', function() {
                has_primary = true;
            });

            sysend.on('__open__', function(data) {
                var id = data.id;
                target_count++;
                if (primary) {
                    sysend.broadcast('__ack__');
                }
                trigger(handlers.open, {
                    count: target_count,
                    primary: data.primary,
                    id: data.id
                });
                if (id === target_id) {
                    trigger(handlers.ready);
                }
            });

            sysend.on('__ack__', function() {
                if (!primary) {
                    trigger(handlers.secondary);
                }
            });

            sysend.on('__close__', function(data) {
                --target_count;
                var last = target_count === 1;
                if (data.wasPrimary && !primary) {
                    has_primary = false;
                }
                var payload = {
                    id: data.id,
                    count: target_count,
                    primary: data.wasPrimary,
                    self: data.id === target_id
                };
                // we need to trigger primary when tab is in different window
                // and is not be hidden
                if (last) {
                    become_primary();
                }
                trigger(handlers.close, payload);
            });

            sysend.on('__window__', function(data) {
                sysend.broadcast('__window_ack__', {
                    id: target_id,
                    origin: data.id,
                    primary: primary
                });
            });

            sysend.on('__message__', function(data) {
                if (data.target === 'primary' && primary) {
                    trigger(handlers.message, data);
                } else if (data.target === target_id) {
                    trigger(handlers.message, data);
                }
            });

            addEventListener('beforeunload', function() {
                sysend.emit('__close__', { id: target_id, wasPrimary: primary });
            }, { capture: true });

            iframe_loaded().then(function() {
                sysend.list().then(function(list) {
                    target_count = list.length;
                    primary = list.length === 0;
                    var found = list.find(function(item) {
                        return item.primary;
                    });
                    if (found || primary) {
                        has_primary = true;
                    }
                    sysend.emit('__open__', {
                        id: target_id,
                        primary: primary
                    });
                    if (primary) {
                        trigger(handlers.primary);
                    }
                });
            });
        }
    }
    return sysend;
});

<MSG> update version

<DFF> @@ -1,5 +1,5 @@
 /**@license
- *  sysend.js - send messages between browser windows/tabs version 1.12.1
+ *  sysend.js - send messages between browser windows/tabs version 1.12.2
  *
  *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>
  *  Released under the MIT license

{'commit': 'diff --git a/README.md b/README.md\nindex 0511fae..4d46519 100644\n--- a/README.md\n+++ b/README.md\n@@ -2,8 +2,8 @@\n   <img src="https://github.com/jcubic/sysend.js/blob/master/assets/logo.svg?raw=true" alt="Sysend.js logo"/>\n </p>\n \n-[![npm](https://img.shields.io/badge/npm-1.12.1-blue.svg)](https://www.npmjs.com/package/sysend)\n-![bower](https://img.shields.io/badge/bower-1.12.1-yellow.svg)\n+[![npm](https://img.shields.io/badge/npm-1.12.2-blue.svg)](https://www.npmjs.com/package/sysend)\n+![bower](https://img.shields.io/badge/bower-1.12.2-yellow.svg)\n ![downloads](https://img.shields.io/npm/dt/sysend.svg)\n [![jsdelivr](https://img.shields.io/jsdelivr/npm/hm/sysend)](https://www.jsdelivr.com/package/npm/sysend)\n \ndiff --git a/package.json b/package.json\nindex 1a6dd88..84ebdc5 100644\n--- a/package.json\n+++ b/package.json\n@@ -1,6 +1,6 @@\n {\n   "name": "sysend",\n-  "version": "1.12.1",\n+  "version": "1.12.2",\n   "description": "Web application synchronization between different tabs",\n   "main": "sysend.js",\n   "typings": "sysend.d.ts",\ndiff --git a/sysend.d.ts b/sysend.d.ts\nindex 4d4d56f..15ae7be 100644\n--- a/sysend.d.ts\n+++ b/sysend.d.ts\n@@ -1,5 +1,5 @@\n /**@license\n- *  sysend.js - send messages between browser windows/tabs version 1.12.1\n+ *  sysend.js - send messages between browser windows/tabs version 1.12.2\n  *\n  *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>\n  *  Released under the MIT license\ndiff --git a/sysend.js b/sysend.js\nindex fac77e9..4b9a7c1 100644\n--- a/sysend.js\n+++ b/sysend.js\n@@ -1,5 +1,5 @@\n /**@license\n- *  sysend.js - send messages between browser windows/tabs version 1.12.1\n+ *  sysend.js - send messages between browser windows/tabs version 1.12.2\n  *\n  *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>\n  *  Released under the MIT license\n', 'commit_subject': 'update version', 'repo_name': 'jcubic/sysend.js', 'committer': {'name': 'Jakub Jankiewicz', 'email': 'e0bbe2c4f545835c937172fe4a26a017e4bbbfcf@onet.pl', 'time_sec': '1661093140', 'tz_offset': '120', 'date': {'seconds': '1661093140'}}, 'commit_tree': '884985e60835f6aa2e84a40ea8ec8fac0d5b745e', 'commit_message': 'update version\n', 'language_name': 'JavaScript', 'license': 'mit', 'hunks': '@@ -1,5 +1,5 @@\n /**@license\n- *  sysend.js - send messages between browser windows/tabs version 1.12.1\n+ *  sysend.js - send messages between browser windows/tabs version 1.12.2\n  *\n  *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>\n  *  Released under the MIT license\n', 'addition_count': 1, 'deletion_count': 1, 'src_file': 'a/sysend.js', 'tgt_file': 'b/sysend.js', 'file_extension': '.js', 'before_file': '/**@license\n *  sysend.js - send messages between browser windows/tabs version 1.12.1\n *\n *  Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>\n *  Released under the MIT license\n *\n *  The idea for localStorage implementation came from this StackOverflow question:\n *  http://stackoverflow.com/q/24182409/387194\n *\n */\n/* global define, module, exports, localStorage, setTimeout */\n(function (root, factory) {\n    if (typeof define === \'function\' && define.amd) {\n        define([\'sysend\'], factory);\n    } else if (typeof exports === \'object\') {\n        module.exports = factory();\n    } else {\n        root.sysend = factory();\n    }\n})(typeof window !== "undefined" ? window : this, function() {\n    var collecting_timeout = 400;\n    // we use prefix so `foo\' event don\'t collide with `foo\' locaStorage value\n    var uniq_prefix = \'___sysend___\';\n    var prefix_re = new RegExp(uniq_prefix);\n    var random_value = Math.random();\n    var serializer = {};\n    // object with user events as keys and values arrays of callback functions\n    var callbacks = {};\n    var has_primary;\n    var iframes = [];\n    var index = 0;\n    var proxy_mode = false;\n    var channel;\n    var primary = true;\n    var force_ls = false;\n    // we use id because storage event is not executed if message was not\n    // changed, and we want it if user send same object twice (before it will\n    // be removed)\n    var id = 0;\n    // identifier for making each call to list unique\n    var list_id = 0;\n\n    // id of the window/tabAnd two-way communication is tracked in \n    var target_id = generate_uuid();\n    var target_count = 1;\n    var domains;\n\n    var handlers = {\n        primary: [],\n        close: [],\n        open: [],\n        secondary: [],\n        message: [],\n        visbility: [],\n        ready: []\n    };\n    var events = Object.keys(handlers);\n    // -------------------------------------------------------------------------\n    var serialize = make_process(serializer, \'to\');\n    var unserialize = make_process(serializer, \'from\');\n    // -------------------------------------------------------------------------\n    var sysend = {\n        id: target_id,\n        broadcast: function(event, data) {\n            if (channel && !force_ls) {\n                channel.postMessage({name: event, data: serialize(data)});\n            } else {\n                set(event, to_json(data));\n                // clean up localstorage\n                setTimeout(function() {\n                    remove(event);\n                }, 0);\n            }\n            send_to_iframes(event, data);\n            return sysend;\n        },\n        emit: function(event, data) {\n            sysend.broadcast(event, data);\n            invoke(event, data);\n            return sysend;\n        },\n        serializer: function(to, from) {\n            if (typeof to !== \'function\' || typeof from !== \'function\') {\n                throw new Error(\'sysend::serializer: Invalid argument, expecting\' +\n                                \' function\');\n            }\n            serializer.to = to;\n            serializer.from = from;\n            return sysend;\n        },\n        proxy: function() {\n            [].slice.call(arguments).forEach(function(url) {\n                if (typeof url === \'string\' && host(url) !== window.location.host) {\n                    domains = domains || [];\n                    domains.push(origin(url));\n                    var iframe = document.createElement(\'iframe\');\n                    iframe.style.width = iframe.style.height = 0;\n                    iframe.style.border = \'none\';\n                    var proxy_url = url;\n                    if (!url.match(/\\.html$/)) {\n                        proxy_url = url.replace(/\\/$/, \'\') + \'/proxy.html\';\n                    }\n                    iframe.addEventListener(\'error\', function handler() {\n                        setTimeout(function() {\n                            throw new Error(\'html proxy file not found on "\' + url +\n                                            \'" url\');\n                        }, 0);\n                        iframe.removeEventListener(\'error\', handler);\n                    });\n                    iframe.addEventListener(\'load\', function handler() {\n                        var win;\n                        // fix for Safari\n                        // https://stackoverflow.com/q/42632188/387194\n                        try {\n                            win = iframe.contentWindow;\n                        } catch(e) {\n                            win = iframe.contentWindow;\n                        }\n                        iframes.push({window: win, node: iframe});\n                        iframe.removeEventListener(\'load\', handler);\n                    });\n                    document.body.appendChild(iframe);\n                    iframe.src = proxy_url;\n                }\n            });\n            if (!arguments.length && is_iframe) {\n                proxy_mode = true;\n            }\n            return sysend;\n        },\n        on: function(event, fn) {\n            if (!callbacks[event]) {\n                callbacks[event] = [];\n            }\n            callbacks[event].push(fn);\n            return sysend;\n        },\n        off: function(event, fn) {\n            if (callbacks[event]) {\n                if (fn) {\n                    for (var i = callbacks[event].length; i--;) {\n                        if (callbacks[event][i] == fn) {\n                            callbacks[event].splice(i, 1);\n                        }\n                    }\n                } else {\n                    callbacks[event] = [];\n                }\n            }\n            return sysend;\n        },\n        track: function(event, fn) {\n            if (events.includes(event)) {\n                handlers[event].push(fn);\n            }\n            return sysend;\n        },\n        untrack: function(event, fn) {\n            if (events.includes(event) && handlers[event].length) {\n                if (fn === undefined) {\n                    handlers[event] = [];\n                } else {\n                    handlers[event] = handlers[event].filter(function(handler) {\n                        return handler !== fn;\n                    });\n                }\n            }\n            return sysend;\n        },\n        post: function(target, data) {\n            return sysend.broadcast(\'__message__\', {\n                target: target,\n                data: data,\n                origin: target_id\n            });\n        },\n        list: function() {\n            var id = list_id++;\n            var marker = { target: target_id, id: id };\n            var timer = delay(sysend.timeout);\n            return new Promise(function(resolve) {\n                var ids = [];\n                sysend.on(\'__window_ack__\', function(data) {\n                    if (data.origin.target === target_id && data.origin.id === id) {\n                        ids.push({\n                            id: data.id,\n                            primary: data.primary\n                        });\n                    }\n                });\n                sysend.broadcast(\'__window__\', { id: marker });\n                timer().then(function() {\n                    resolve(ids);\n                });\n            });\n        },\n        channel: function() {\n            domains = [].slice.apply(arguments).map(origin);\n            return sysend;\n        },\n        isPrimary: function() {\n            return primary;\n        },\n        useLocalStorage: function(toggle) {\n            if (typeof toggle === \'boolean\') {\n                force_ls = toggle;\n            } else {\n                force_ls = true;\n            }\n        },\n    };\n    // -------------------------------------------------------------------------\n    Object.defineProperty(sysend, \'timeout\', {\n        enumerable: true,\n        get: function() {\n            return collecting_timeout;\n        },\n        set: function(value) {\n            if (typeof value === \'number\' && !isNaN(value)) {\n                collecting_timeout = value;\n            }\n        }\n    });\n    // -------------------------------------------------------------------------\n    var host = (function() {\n        if (typeof URL !== \'undefined\') {\n            return function(url) {\n                url = new URL(url);\n                return url.host;\n            };\n        }\n        var a = document.createElement(\'a\');\n        return function(url) {\n            a.href = url;\n            return a.host;\n        };\n    })();\n    // -------------------------------------------------------------------------\n    function delay(time) {\n        return function() {\n            return new Promise(function(resolve) {\n                setTimeout(resolve, time);\n            });\n        };\n    }\n    // -------------------------------------------------------------------------\n    var origin = (function() {\n        if (window.URL) {\n            return function(url) {\n                return new URL(url).origin;\n            };\n        }\n        var a = document.createElement(\'a\');\n        return function origin(url) {\n            a.href = url;\n            return a.origin;\n        };\n    })();\n    // -------------------------------------------------------------------------\n    // :: show only single message of this kind\n    // -------------------------------------------------------------------------\n    var warn_messages = [];\n    function warn(message) {\n        if (!warn_messages.includes(message)) {\n            warn_messages.push(message);\n            if (console && console.warn) {\n                console.warn(message);\n            } else {\n                setTimeout(function() {\n                    throw new Error(message);\n                }, 0);\n            }\n        }\n    }\n    // -------------------------------------------------------------------------\n    function iframe_loaded() {\n        var iframes = Array.from(document.querySelectorAll(\'iframe\'));\n        return Promise.all(iframes.filter(function(iframe) {\n            return iframe.src;\n        }).map(function(iframe) {\n            return new Promise(function(resolve, reject) {\n                iframe.addEventListener(\'load\', resolve, true);\n                iframe.addEventListener(\'error\', reject, true);\n            });\n        })).then(delay(sysend.timeout));\n        // delay is required, something with browser, it\'s not intialized\n        // properly. The number was picked by experimentation\n    }\n    // -------------------------------------------------------------------------\n    // :: valid sysend message\n    // -------------------------------------------------------------------------\n    function is_sysend_post_message(e) {\n        return typeof e.data === \'string\' && e.data.match(prefix_re);\n    }\n    // -------------------------------------------------------------------------\n    function is_valid_origin(origin) {\n        if (!domains) {\n            warn(\'Call sysend.channel() on iframe to restrict domains that can \'+\n                 \'use sysend channel\');\n            return true;\n        }\n        var valid = domains.includes(origin);\n        if (!valid) {\n            warn(origin + \' domain is not on the list of allowed \'+\n                 \'domains use sysend.channel() on iframe to allow\'+\n                 \' access to this domain\');\n        }\n        return valid;\n    }\n    // -------------------------------------------------------------------------\n    // :: ref: https://stackoverflow.com/a/8809472/387194\n    // :: license: Public Domain/MIT\n    // -------------------------------------------------------------------------\n    function generate_uuid() {\n        var d = new Date().getTime();\n        //Time in microseconds since page-load or 0 if unsupported\n        var d2 = (performance && performance.now && (performance.now() * 1000)) || 0;\n        return \'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\'.replace(/[xy]/g, function(c) {\n            var r = Math.random() * 16;\n            if (d > 0) { // Use timestamp until depleted\n                r = (d + r)%16 | 0;\n                d = Math.floor(d/16);\n            } else { // Use microseconds since page-load if supported\n                r = (d2 + r)%16 | 0;\n                d2 = Math.floor(d2/16);\n            }\n            return (c === \'x\' ? r : (r & 0x3 | 0x8)).toString(16);\n        });\n    }\n    // -------------------------------------------------------------------------\n    function trigger(arr) {\n        var args = [].slice.call(arguments, 1);\n        arr.forEach(function(fn) {\n            fn.apply(null, args);\n        });\n    }\n    // -------------------------------------------------------------------------\n    function get(key) {\n        return localStorage.getItem(uniq_prefix + key);\n    }\n    // -------------------------------------------------------------------------\n    function set(key, value) {\n        // storage event is not fired when value is set first time\n        if (id == 0) {\n            localStorage.setItem(uniq_prefix + key, random_value);\n        }\n        localStorage.setItem(uniq_prefix + key, value);\n    }\n    // -------------------------------------------------------------------------\n    function remove(key) {\n        localStorage.removeItem(uniq_prefix + key);\n    }\n    // -------------------------------------------------------------------------\n    function make_process(object, prop) {\n        var labels = {\n            from: \'Unserialize\',\n            to: \'Serialize\'\n        };\n        var prefix_message = labels[prop] + \' Error: \';\n        return function(data) {\n            var fn = object[prop];\n            try {\n                if (fn) {\n                    return fn(data);\n                }\n                return data;\n            } catch (e) {\n                warn(prefix_message + e.message);\n            }\n        };\n    }\n    // -------------------------------------------------------------------------\n    // ref: https://stackoverflow.com/a/326076/387194\n    // -------------------------------------------------------------------------\n    var is_iframe = (function is_iframe() {\n        try {\n            return window.self !== window.top;\n        } catch (e) {\n            return true;\n        }\n    })();\n    // -------------------------------------------------------------------------\n    var is_private_mode = (function is_private_mode() {\n        try {\n            localStorage.setItem(uniq_prefix, 1);\n            localStorage.removeItem(uniq_prefix);\n            return false;\n        } catch (e) {\n            return true;\n        }\n    })();\n    // -------------------------------------------------------------------------\n    function is_proxy_iframe() {\n        return is_iframe && proxy_mode;\n    }\n    // -------------------------------------------------------------------------\n    function send_to_iframes(key, data) {\n        // propagate events to iframes\n        iframes.forEach(function(iframe) {\n            var payload = {\n              name: uniq_prefix,\n              key: key,\n              data: data\n            };\n            if (is_valid_origin(origin(iframe.node.src))) {\n                iframe.window.postMessage(JSON.stringify(payload), "*");\n            }\n        });\n    }\n    // -------------------------------------------------------------------------\n    function to_json(input) {\n        // save random_value in storage to fix issue in IE that storage event\n        // is fired on same page where setItem was called\n        var obj = [id++, random_value];\n        // undefined in array get stringified as null\n        if (typeof input != \'undefined\') {\n            obj.push(input);\n        }\n        var data = serialize(obj);\n        if (data === obj) {\n            return JSON.stringify(obj);\n        }\n        return data;\n    }\n    // -------------------------------------------------------------------------\n    function from_json(json) {\n        var result = unserialize(json);\n        if (result === json) {\n            return JSON.parse(json);\n        }\n        return result;\n    }\n    // -------------------------------------------------------------------------\n    function invoke(key, data) {\n        callbacks[key].forEach(function(fn) {\n            fn(data, key);\n        });\n    }\n    // -------------------------------------------------------------------------\n    function init_visiblity() {\n        // ref: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API\n        var hidden, visibilityChange;\n        if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support\n            hidden = "hidden";\n            visibilityChange = "visibilitychange";\n        } else if (typeof document.msHidden !== "undefined") {\n            hidden = "msHidden";\n            visibilityChange = "msvisibilitychange";\n        } else if (typeof document.webkitHidden !== "undefined") {\n            hidden = "webkitHidden";\n            visibilityChange = "webkitvisibilitychange";\n        }\n        if (typeof document.addEventListener === \'function\' && hidden) {\n            document.addEventListener(visibilityChange, function() {\n                trigger(handlers.visbility, !document[hidden]);\n            }, false);\n        }\n    }\n    // -------------------------------------------------------------------------\n    function become_primary() {\n        primary = true;\n        trigger(handlers.primary);\n        sysend.emit(\'__primary__\');\n    }\n    // -------------------------------------------------------------------------\n    function document_ready() {\n        return [\'interactive\', \'complete\'].indexOf(document.readyState) !== -1;\n    }\n    // -------------------------------------------------------------------------\n    if (document_ready()) {\n        init();\n    } else {\n        window.addEventListener(\'load\', function() {\n            setTimeout(init, 0);\n        });\n    }\n    function setup_ls() {\n        // we need to clean up localStorage if broadcast called on unload\n        // because setTimeout will never fire, even setTimeout 0\n        var re = new RegExp(\'^\' + uniq_prefix);\n        for(var key in localStorage) {\n            if (key.match(re)) {\n                localStorage.removeItem(key);\n            }\n        }\n        window.addEventListener(\'storage\', function(e) {\n            // prevent event to be executed on remove in IE\n            if (e.key.match(re) && index++ % 2 === 0) {\n                var key = e.key.replace(re, \'\');\n                if (callbacks[key]) {\n                    var value = e.newValue || get(key);\n                    if (value && value != random_value) {\n                        var obj = from_json(value);\n                        // don\'t call on remove\n                        if (obj && obj[1] != random_value) {\n                            invoke(key, obj[2]);\n                        }\n                    }\n                }\n            }\n        }, false);\n    }\n    // -------------------------------------------------------------------------\n    function init() {\n        if (typeof window.BroadcastChannel === \'function\') {\n            channel = new window.BroadcastChannel(uniq_prefix);\n            channel.addEventListener(\'message\', function(event) {\n                if (event.target.name === uniq_prefix) {\n                    if (is_proxy_iframe()) {\n                        var payload = {\n                          name: uniq_prefix,\n                          data: event.data,\n                          iframe_id: target_id\n                        };\n                        if (is_valid_origin(origin(document.referrer))) {\n                            window.parent.postMessage(JSON.stringify(payload), "*");\n                        }\n                    } else {\n                        var key = event.data && event.data.name;\n                        if (callbacks[key]) {\n                            invoke(key, unserialize(event.data.data));\n                        }\n                    }\n                }\n            });\n        } else if (is_private_mode) {\n            warn(\'Your browser don\\\'t support localStorgage. \' +\n                 \'In Safari this is most of the time because \' +\n                 \'of "Private Browsing Mode"\');\n        }\n        if (!is_private_mode) {\n            setup_ls();\n        }\n\n        if (is_proxy_iframe()) {\n            window.addEventListener(\'message\', function(e) {\n                if (is_sysend_post_message(e) && is_valid_origin(e.origin)) {\n                    try {\n                        var payload = JSON.parse(e.data);\n                        if (payload && payload.name === uniq_prefix) {\n                            var data = unserialize(payload.data);\n                            sysend.broadcast(payload.key, data);\n                        }\n                    } catch(e) {\n                        // ignore wrong JSON, the message don\'t came from Sysend\n                        // even that the string have unix_prefix, this is just in case\n                    }\n                }\n            });\n        } else {\n            init_visiblity();\n\n            sysend.track(\'visbility\', function(visible) {\n                if (visible && !has_primary) {\n                    become_primary();\n                }\n            });\n\n            sysend.on(\'__primary__\', function() {\n                has_primary = true;\n            });\n\n            sysend.on(\'__open__\', function(data) {\n                var id = data.id;\n                target_count++;\n                if (primary) {\n                    sysend.broadcast(\'__ack__\');\n                }\n                trigger(handlers.open, {\n                    count: target_count,\n                    primary: data.primary,\n                    id: data.id\n                });\n                if (id === target_id) {\n                    trigger(handlers.ready);\n                }\n            });\n\n            sysend.on(\'__ack__\', function() {\n                if (!primary) {\n                    trigger(handlers.secondary);\n                }\n            });\n\n            sysend.on(\'__close__\', function(data) {\n                --target_count;\n                var last = target_count === 1;\n                if (data.wasPrimary && !primary) {\n                    has_primary = false;\n                }\n                var payload = {\n                    id: data.id,\n                    count: target_count,\n                    primary: data.wasPrimary,\n                    self: data.id === target_id\n                };\n                // we need to trigger primary when tab is in different window\n                // and is not be hidden\n                if (last) {\n                    become_primary();\n                }\n                trigger(handlers.close, payload);\n            });\n\n            sysend.on(\'__window__\', function(data) {\n                sysend.broadcast(\'__window_ack__\', {\n                    id: target_id,\n                    origin: data.id,\n                    primary: primary\n                });\n            });\n\n            sysend.on(\'__message__\', function(data) {\n                if (data.target === \'primary\' && primary) {\n                    trigger(handlers.message, data);\n                } else if (data.target === target_id) {\n                    trigger(handlers.message, data);\n                }\n            });\n\n            addEventListener(\'beforeunload\', function() {\n                sysend.emit(\'__close__\', { id: target_id, wasPrimary: primary });\n            }, { capture: true });\n\n            iframe_loaded().then(function() {\n                sysend.list().then(function(list) {\n                    target_count = list.length;\n                    primary = list.length === 0;\n                    var found = list.find(function(item) {\n                        return item.primary;\n                    });\n                    if (found || primary) {\n                        has_primary = true;\n                    }\n                    sysend.emit(\'__open__\', {\n                        id: target_id,\n                        primary: primary\n                    });\n                    if (primary) {\n                        trigger(handlers.primary);\n                    }\n                });\n            });\n        }\n    }\n    return sysend;\n});\n', 'message': 'update version\n', 'author_name': 'Jakub Jankiewicz', 'lang': 'js'}