您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Inter-window (cross-tab) communication library.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/405935/819599/IWC.js
- //https://github.com/slimjack/IWC
- var SJ = SJ || {};
- SJ.ns = function createNameSpace(namespace) {
- var nsparts = namespace.split(".");
- var parent = SJ;
- if (nsparts[0] === "SJ") {
- nsparts = nsparts.slice(1);
- }
- for (var i = 0; i < nsparts.length; i++) {
- var partname = nsparts[i];
- if (typeof parent[partname] === "undefined") {
- parent[partname] = {};
- }
- parent = parent[partname];
- }
- return parent;
- };
- //https://github.com/slimjack/IWC
- (function (scope) {
- var fixedHandlers = {};
- var handlerId = 0;
- var toString = ({}).toString;
- var basicUtils = {
- appName: window.applicationName || 'DEFAULT',
- generateGUID: function () {
- var d = new Date().getTime();
- var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
- var r = (d + Math.random() * 16) % 16 | 0;
- d = Math.floor(d / 16);
- return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
- });
- return uuid;
- },
- callback: function (callback) {
- if (SJ.isFunction(callback)) {
- var args = Array.prototype.slice.call(arguments, 1);
- callback.apply(window, args);
- }
- },
- windowOn: function (eventName, handler) {
- handler.handlerId = ++handlerId;
- var fixedHandler = function (event) {
- event = event || window.event;
- handler(event);
- };
- fixedHandlers[handler.handlerId] = handler;
- if (window.addEventListener) {
- window.addEventListener(eventName, fixedHandler, false);
- } else if (window.attachEvent) {
- window.attachEvent('on' + eventName, fixedHandler);
- }
- },
- windowUn: function (eventName, handler) {
- if (window.removeEventListener) {
- window.removeEventListener(eventName, fixedHandlers[handler.handlerId], false);
- } else if (window.detachEvent) {
- window.detachEvent('un' + eventName, fixedHandlers[handler.handlerId]);
- }
- delete fixedHandlers[handler.handlerId];
- },
- isIE: function () {
- var isIE11 = (!(window.ActiveXObject) && ('ActiveXObject' in window));
- if (isIE11) {
- return 11;
- }
- var myNav = navigator.userAgent.toLowerCase();
- return (myNav.indexOf('msie') !== -1) ? parseInt(myNav.split('msie')[1]) : false;
- },
- copy: function (dst, src) {
- var i;
- for (i in src) {
- dst[i] = src[i];
- }
- return dst;
- },
- emptyFn: function() {},
- isEmpty: function (value) {
- return (value == null) || value === '' || (SJ.isArray(value) && value.length === 0);
- },
- isArray: ('isArray' in Array) ? Array.isArray : function (value) {
- return toString.call(value) === '[object Array]';
- },
- isDate: function (value) {
- return toString.call(value) === '[object Date]';
- },
- isObject: function (value) {
- return value !== null && value !== undefined && toString.call(value) === '[object Object]';
- },
- isPrimitive: function (value) {
- var type = typeof value;
- return type === 'number' || type === 'string' || type === 'boolean';
- },
- isFunction: function (value) {
- return !!value && toString.call(value) === '[object Function]';
- },
- isNumber: function (value) {
- return typeof value === 'number' && isFinite(value);
- },
- isNumeric: function (value) {
- return !isNaN(parseFloat(value)) && isFinite(value);
- },
- isString: function (value) {
- return typeof value === 'string';
- },
- isBoolean: function (value) {
- return typeof value === 'boolean';
- }
- };
- basicUtils.copy(scope, basicUtils);
- })(SJ);
- //https://github.com/slimjack/IWC
- (function (scope) {
- var ObjectHelper = {
- each: function (obj, fn) {
- for (var propName in obj) {
- if (obj.hasOwnProperty(propName)) {
- if (fn(obj[propName], propName) === false) {
- break;
- }
- }
- }
- }
- };
- SJ.copy(scope, ObjectHelper);
- })(SJ.ns('Object'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- function decorate(target) {
- var listeners = {};
- function removeListener(eventName, listener) {
- for (var i = 0; i < listeners[eventName].length; i++) {
- if (listeners[eventName][i] === listener) {
- listeners[eventName].splice(i, 1);
- }
- }
- }
- function getListener(eventName, listenerFn, scope) {
- var eventListeners = listeners[eventName];
- if (eventListeners) {
- for (var i = 0; i < eventListeners.length; i++) {
- if (eventListeners[i].fn === listenerFn && eventListeners[i].scope === scope) {
- return eventListeners[i];
- }
- }
- }
- return null;
- }
- target.on = function (eventName, listenerFn, scope) {
- if (!listeners[eventName]) {
- listeners[eventName] = [];
- }
- if (!getListener(eventName, listenerFn, scope)) {
- listeners[eventName].push({
- fn: listenerFn,
- scope: scope
- });
- }
- };
- target.once = function (eventName, listenerFn, scope) {
- if (!getListener(eventName, listenerFn, scope)) {
- listeners[eventName] = listeners[eventName] || [];
- listeners[eventName].push({
- fn: listenerFn,
- scope: scope,
- single: true
- });
- }
- };
- target.un = function (eventName, listenerFn, scope) {
- var listener = getListener(eventName, listenerFn, scope);
- if (listener) {
- removeListener(eventName, listener);
- }
- };
- target.fire = function (eventName) {
- var eventListeners = listeners[eventName];
- if (eventListeners) {
- eventListeners = [].concat(eventListeners);
- var args = Array.prototype.slice.call(arguments, 1);
- for (var i = 0; i < eventListeners.length; i++) {
- eventListeners[i].fn.apply(eventListeners[i].scope, args);
- if (eventListeners[i].single) {
- removeListener(eventName, eventListeners[i]);
- }
- }
- }
- };
- return target;
- };
- function Observable() {
- decorate(this);
- };
- Observable.decorate = function (target, onlyPublic) {
- var observable;
- if (onlyPublic) {
- observable = new Observable();
- target.on = observable.on;
- target.un = observable.un;
- target.once = observable.once;
- } else {
- observable = decorate(target);
- }
- return observable;
- };
- scope.Observable = Observable;
- })(SJ.ns('utils'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- //LocalStorage wrapper is used to abstract from some issues related to different browsers
- var originalLocalStorage = window.localStorage;
- if (typeof originalLocalStorage === 'undefined') {
- originalLocalStorage = {
- getItem: function () { },
- setItem: function () { },
- removeItem: function () { }
- };
- alert('Local storage is not supported on this browser. Some features will not work.');
- }
- var isIE11 = SJ.isIE() === 11;
- var lastStorageEventKey;
- var lastStorageEventValue;
- var observableOnlyExternal = new SJ.utils.Observable();
- //observable is used only for events from other windows
- var observableAll;
- //observableAll is used for events from other windows and from this window too
- var thisWindowEventsBug = (SJ.isIE() === 10) || (SJ.isIE() === 9);
- /*IE9 and IE10 both incorrectly fire the storage event in ALL windows/tabs,
- https://connect.microsoft.com/IE/feedback/details/774798/localstorage-event-fired-in-source-window
- */
- if (!thisWindowEventsBug) {
- //if browser works according localStorage specification, this window events will be fired from this localStorage wrapper
- observableAll = new SJ.utils.Observable();
- };
- var onStorage = isIE11 ?
- function (event) {
- event = event || window.event;
- // Workaround for IE11 (value from storage is not actual in event handler)
- lastStorageEventKey = event.key;
- lastStorageEventValue = event.newValue;
- observableOnlyExternal.fire('storage', event);
- observableAll.fire('storage', event);
- } : thisWindowEventsBug ?
- function (event) {
- event = event || window.event;
- observableOnlyExternal.fire('storage', event);
- } :
- function (event) {
- event = event || window.event;
- observableOnlyExternal.fire('storage', event);
- observableAll.fire('storage', event);
- };
- if (window.addEventListener) {
- var wnd = isIE11 ? window.top : window; // Workaround for IE11 (for iframes)
- wnd.addEventListener('storage', onStorage, false);
- if (wnd != window) {
- window.addEventListener('unload', function () {
- wnd.removeEventListener('storage', onStorage, false);
- });
- }
- } else if (window.attachEvent) {
- document.attachEvent('onstorage', onStorage);
- }
- scope.localStorage = {
- onChanged: function (fn, scope, listenThisWindow) {
- /*If listenThisWindow is true then event handler will be called for events fired from this window and from other.
- Otherwise it will be called only for events from other windows
- If browser has an event bug (storage recieved events from this window), then listenThisWindow is ignored, because this behaviour uncontrollable in buggy browser*/
- if (listenThisWindow && !thisWindowEventsBug) {
- observableAll.on('storage', fn, scope);
- } else {
- observableOnlyExternal.on('storage', fn, scope);
- }
- },
- onceChanged: function (fn, scope, listenThisWindow) {
- if (listenThisWindow && !thisWindowEventsBug) {
- observableAll.once('storage', fn, scope);
- } else {
- observableOnlyExternal.once('storage', fn, scope);
- }
- },
- unChanged: function (fn, scope) {
- observableOnlyExternal.un('storage', fn, scope);
- if (!thisWindowEventsBug) {
- observableAll.un('storage', fn, scope);
- }
- },
- getItem: function (key) {
- if (isIE11 && (lastStorageEventKey === key)) {
- return lastStorageEventValue;
- }
- return originalLocalStorage.getItem(key);
- },
- setItem: thisWindowEventsBug ? function (key, value) {
- if (isIE11 && (lastStorageEventKey === key)) {
- lastStorageEventValue = value;
- }
- originalLocalStorage.setItem(key, value);
- } : function (key, value) {
- var oldValue = this.getItem(key);
- if (isIE11 && (lastStorageEventKey === key)) {
- lastStorageEventValue = value;
- }
- originalLocalStorage.setItem(key, value);
- var event = {
- key: key,
- oldValue: oldValue,
- newValue: value,
- url: window.location.href.toString()
- };
- observableAll.fire('storage', event);
- },
- removeItem: thisWindowEventsBug ? function (key) {
- if (isIE11 && (lastStorageEventKey === key)) {
- lastStorageEventValue = null;
- }
- originalLocalStorage.removeItem(key);
- } : function (key) {
- var oldValue = this.getItem(key);
- if (isIE11 && (lastStorageEventKey === key)) {
- lastStorageEventValue = null;
- }
- originalLocalStorage.removeItem(key);
- var event = {
- key: key,
- oldValue: oldValue,
- newValue: null,
- url: window.location.href.toString()
- };
- observableAll.fire('storage', event);
- },
- forEach: isIE11 ? function (fn) {
- for (var i = 0; i < originalLocalStorage.length; i++) {
- var itemKey = originalLocalStorage.key(i);
- var itemValue;
- if (itemKey === lastStorageEventKey) {
- itemValue = lastStorageEventValue;
- } else {
- itemValue = originalLocalStorage.getItem(itemKey);
- }
- if (fn(itemKey, itemValue) === false) {
- break;
- }
- }
- } : function (fn) {
- for (var i = 0; i < originalLocalStorage.length; i++) {
- var itemKey = originalLocalStorage.key(i);
- var itemValue = originalLocalStorage.getItem(itemKey);
- if (fn(itemKey, itemValue) === false) {
- break;
- }
- }
- },
- setVersion: function (storagePrefix, version) {
- var me = this;
- var currentVersion = me.getItem(storagePrefix);
- if (currentVersion !== version) {
- var itemsToRemove = [];
- me.forEach(function (key) {
- if (key.substr(0, storagePrefix.length) === storagePrefix) {
- itemsToRemove.push(key);
- }
- });
- itemsToRemove.forEach(function (key) {
- me.removeItem(key);
- });
- me.setItem(storagePrefix, version);
- }
- }
- };
- })(SJ);
- //https://github.com/slimjack/IWC
- (function (scope) {
- var localStoragePerfix = 'IWC_' + SJ.appName;
- scope.getLocalStoragePrefix = function () {
- return localStoragePerfix;
- };
- scope.$version = '0.1.3';
- SJ.localStorage.setVersion(localStoragePerfix, scope.$version);
- })(SJ.ns('iwc'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var lockIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_TLOCK_';
- var lockTimeout = 3 * 1000;
- var lockCheckInterval = 50;
- var lockerId = SJ.generateGUID();
- SJ.localStorage.onChanged(onStorageChanged);
- var observable = new SJ.utils.Observable();
- function on(fn) {
- observable.on('storagechanged', fn);
- };
- function un(fn) {
- observable.un('storagechanged', fn);
- };
- function fire() {
- observable.fire('storagechanged');
- };
- function interlockedCall(lockId, fn) {
- var executed = false;
- var listening = false;
- var listeningTimer = null;
- //if testingMode set, lockTimeout may be defined from outside and lock is not released automatically after fn execution
- var _lockTimeout = scope.testingMode ? scope.lockTimeout || lockTimeout : lockTimeout;
- var listen = function () {
- if (!listening) {
- on(tryLock);
- listeningTimer = window.setInterval(tryLock, lockCheckInterval);
- listening = true;
- }
- };
- var stopListening = function () {
- if (listening) {
- un(tryLock);
- window.clearInterval(listeningTimer);
- listening = false;
- }
- };
- var tryLock = function () {
- if (executed) return;
- var now = (new Date()).getTime();
- //begin critical section =============================================
- var activeLock = getLock(lockId);
- if (activeLock && now - activeLock.timestamp < _lockTimeout) {
- listen();
- return;
- }
- executed = true;
- stopListening();
- setLock(lockId, now);
- //end critical section================================================
- //Wait for some time to be sure that during critical section the lock was not intercepted. And then continue lock flow
- var deferredTimer = window.setTimeout(function () {
- window.clearTimeout(deferredTimer);
- var activeLock = getLock(lockId);
- if (!activeLock || (activeLock.timestamp === now && activeLock.lockerId === lockerId)) {
- //The lock is successfully captured
- stopListening();
- if (!activeLock) {
- setLock(lockId, now);
- }
- fn();
- if (!scope.testingMode) {
- removeLock(lockId);
- }
- } else {
- //The lock was intercepted
- executed = false;
- listen();
- }
- }, 10);//This timeout must be bigger than execution time of critical section.
- //For modern computers critical section execution time is less than 1 ms
- };
- tryLock();
- };
- function getLock (lockId) {
- var lockStorageId = lockIdPrefix + lockId;
- var serializedLock = SJ.localStorage.getItem(lockStorageId);
- var result = null;
- if (serializedLock) {
- var parts = serializedLock.split('.');
- result = {
- timestamp: parseInt(parts[0]) || 0,
- lockerId: parts[1]
- };
- }
- return result;
- };
- function setLock (lockId, timestamp) {
- var lockStorageId = lockIdPrefix + lockId;
- SJ.localStorage.setItem(lockStorageId, timestamp + '.' + lockerId);
- };
- function removeLock (lockId) {
- var lockStorageId = lockIdPrefix + lockId;
- SJ.localStorage.removeItem(lockStorageId);
- };
- function onStorageChanged (event) {
- if (event.key) {
- var valueIsRemoved = !event.newValue && !!event.oldValue;//lock functionality needs to know only about removing of items in localStorage
- if (valueIsRemoved && (event.key.substr(0, lockIdPrefix.length) === lockIdPrefix)) {//check that event is related to locks
- fire();
- }
- } else {
- fire();//For IE8. IE8 doesn't provide any details about storage changes
- }
- };
- scope.interlockedCall = interlockedCall;
- SJ.interlockedCall = interlockedCall;
- })(SJ.ns('iwc.Lock'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var SharedData = function (dataId) {
- var me = this;
- me._dataId = dataId;
- me._observable = new SJ.utils.Observable();
- me._serializedData = SJ.localStorage.getItem(me._dataId);
- SJ.localStorage.onChanged(me.onStorageChanged, me, true);
- };
- SharedData.prototype = {
- constructor: SharedData,
- get: function () {
- var me = this;
- me._serializedData = SJ.localStorage.getItem(me._dataId);
- var data = null;
- if (me._serializedData) {
- data = JSON.parse(me._serializedData);
- }
- return data;
- },
- set: function (value, callback) {
- var me = this;
- SJ.iwc.Lock.interlockedCall(me._dataId, function () {
- me.writeToStorage(value);
- SJ.callback(callback);
- });
- },
- change: function (delegate) {
- var me = this;
- SJ.iwc.Lock.interlockedCall(me._dataId, function () {
- var data = me.get();
- data = delegate(data);
- me.writeToStorage(data);
- });
- },
- onChanged: function (fn, scope) {
- var me = this;
- me._observable.on('changed', fn, scope);
- },
- onceChanged: function (fn, scope) {
- var me = this;
- me._observable.once('changed', fn, scope);
- },
- unsubscribe: function (fn, scope) {
- var me = this;
- me._observable.un('changed', fn, scope);
- },
- //region Private
- writeToStorage: function (data) {
- var me = this;
- var serializedData = data !== null ? JSON.stringify(data) : '';
- SJ.localStorage.setItem(me._dataId, serializedData);
- },
- onStorageChanged: function (event) {
- var me = this;
- if (!event.key || (event.key === me._dataId)) {
- var serializedData = SJ.localStorage.getItem(me._dataId);
- if (serializedData !== me._serializedData) {
- me._serializedData = serializedData;
- var data = null;
- if (serializedData) {
- data = JSON.parse(serializedData);
- }
- me._observable.fire('changed', data);
- }
- }
- }
- //endregion
- };
- scope.SharedData = SharedData;
- })(SJ.ns('iwc'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var busNodeId = SJ.generateGUID();
- var observableOnlyExternal = new SJ.utils.Observable();
- var observableAll = new SJ.utils.Observable();//for subscribers which listen for all events including genereted from this window
- var storageId = SJ.iwc.getLocalStoragePrefix() + '_EBUS';
- function onStorageChanged (event) {
- if ((event.key === storageId) && event.newValue) {
- var busEvent = JSON.parse(event.newValue);
- if (busEvent.senderBusNodeId !== busNodeId) {
- observableOnlyExternal.fire.apply(window, busEvent.args);
- observableAll.fire.apply(window, busEvent.args);
- }
- }
- };
- function fire () {
- var busEvent = {
- senderBusNodeId: busNodeId,
- args: Array.prototype.slice.call(arguments, 0)
- };
- var serializedBusEvent = JSON.stringify(busEvent);
- SJ.localStorage.setItem(storageId, serializedBusEvent);
- observableAll.fire.apply(window, busEvent.args);
- };
- SJ.localStorage.onChanged(onStorageChanged);
- SJ.copy(scope, {
- on: function (eventName, fn, scope, listenThisWindow) {
- if (listenThisWindow) {
- observableAll.on(eventName, fn, scope);
- } else {
- observableOnlyExternal.on(eventName, fn, scope);
- }
- },
- once: function (eventName, fn, scope, listenThisWindow) {
- if (listenThisWindow) {
- observableAll.once(eventName, fn, scope);
- } else {
- observableOnlyExternal.once(eventName, fn, scope);
- }
- },
- un: function (eventName, fn, scope, listenThisWindow) {
- if (listenThisWindow) {
- observableAll.un(eventName, fn, scope);
- } else {
- observableOnlyExternal.un(eventName, fn, scope);
- }
- },
- fire: fire
- });
- })(SJ.ns('iwc.EventBus'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var windowRecordLifeTime = 5000;
- var obsoleteRequestTimeFrame = 2000;
- var openWindows = {};
- var obsoleteWindows = {};
- var thisWindowId = window.name || SJ.generateGUID();
- var isWindowMonitorReady = false;
- var observable = new SJ.utils.Observable();
- var storageIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_WND_';
- var windowRecordUpdatingPeriod = windowRecordLifeTime / 2;
- var storageId = storageIdPrefix + thisWindowId;
- SJ.windowOn('unload', onWindowUnload);
- SJ.localStorage.onChanged(onStorageChanged);
- SJ.iwc.EventBus.on('windowfocusrequest', onWindowFocusRequest);
- SJ.iwc.EventBus.on('windowisaliverequest', onWindowIsAliveRequest);
- SJ.iwc.EventBus.on('windowisaliveresponce', onWindowIsAliveResponce);
- updateDataInStorage();
- window.setInterval(updateDataInStorage, windowRecordUpdatingPeriod);
- function onWindowUnload() {
- SJ.localStorage.removeItem(storageId);
- };
- function onWindowIsAliveRequest(windowId) {
- if (windowId === thisWindowId) {
- updateDataInStorage();
- SJ.iwc.EventBus.fire('windowisaliveresponce', windowId);
- }
- };
- function onWindowIsAliveResponce(windowId) {
- if ((windowId !== thisWindowId) && obsoleteWindows[windowId]) {
- delete obsoleteWindows[windowId];
- }
- };
- function onStorageChanged(event) {
- if (event.key) {
- if (event.key.substr(0, storageIdPrefix.length) === storageIdPrefix) {//check that event is related to WindowsManager
- updateDataFromStorage();
- }
- } else {
- updateDataFromStorage();//For IE8. IE8 doesn't provide any details about storage changes
- }
- };
- function getOpenWindows() {
- var windows = {};
- SJ.localStorage.forEach(function (itemKey, itemValue) {
- if (itemValue && (itemKey.substr(0, storageIdPrefix.length) === storageIdPrefix)) {
- var windowId = itemKey.substr(storageIdPrefix.length);
- windows[windowId] = parseInt(itemValue);
- }
- });
- removeObsoleteRecords(windows);
- return windows;
- };
- function updateDataInStorage() {
- updateDataFromStorage();
- var now = (new Date()).getTime();
- openWindows[thisWindowId] = now;
- SJ.localStorage.setItem(storageId, now);
- if (!isWindowMonitorReady) {
- isWindowMonitorReady = true;
- observable.fire('windowsmanagerready');
- }
- };
- function removeObsoleteRecords(windows) {
- var now = (new Date()).getTime();
- for (var windowId in windows) {
- if (windows.hasOwnProperty(windowId) && windowId !== thisWindowId) {
- var recordAge = now - windows[windowId];
- if ((recordAge > 2 * windowRecordLifeTime)
- || ((recordAge > windowRecordLifeTime) && obsoleteWindows[windowId] && (now - obsoleteWindows[windowId] > obsoleteRequestTimeFrame))) {
- delete windows[windowId];
- SJ.localStorage.removeItem(storageIdPrefix + windowId);
- } else if (recordAge > windowRecordLifeTime) {
- if (!obsoleteWindows[windowId]) {
- obsoleteWindows[windowId] = now;
- SJ.iwc.EventBus.fire('windowisaliverequest', windowId);
- }
- } else if (obsoleteWindows[windowId]) {
- delete obsoleteWindows[windowId];
- }
- }
- }
- for (var windowId in obsoleteWindows) {
- if (obsoleteWindows.hasOwnProperty(windowId) && !windows.hasOwnProperty(windowId)) {
- delete obsoleteWindows[windowId];
- }
- }
- };
- function isWindowOpen(windowId) {
- return !!openWindows[windowId];
- };
- function updateDataFromStorage() {
- var newOpenWindows = getOpenWindows();
- var newWindows = [];
- for (var windowId in newOpenWindows) {
- if (newOpenWindows.hasOwnProperty(windowId) && !openWindows[windowId]) {
- newWindows.push(windowId);
- }
- }
- var closedWindows = [];
- for (var windowId in openWindows) {
- if (openWindows.hasOwnProperty(windowId) && !newOpenWindows[windowId]) {
- closedWindows.push(windowId);
- }
- }
- openWindows = newOpenWindows;
- if (newWindows.length || closedWindows.length) {
- onWindowsChanged(newWindows, closedWindows);
- }
- };
- function onWindowsChanged(newWindows, closedWindows) {
- observable.fire('windowschanged', newWindows, closedWindows);
- };
- function onWindowFocusRequest (windowId) {
- if (windowId === thisWindowId) {
- window.focus();
- blinkTitle();
- }
- };
- var blinkCounter = 0;
- function blinkTitle() {
- if (blinkCounter) {
- return;
- }
- var blinkPeriod = 500;//ms
- var numOfBlinks = 3;
- var title = document.title;
- var isTitleVisible = false;
- blinkCounter = numOfBlinks * 2;
- var changeTitle = function () {
- if (isTitleVisible) {
- document.title = title;
- } else {
- document.title = "******";
- }
- isTitleVisible = !isTitleVisible;
- blinkCounter--;
- if (blinkCounter) {
- window.setTimeout(changeTitle, blinkPeriod);
- }
- };
- window.setTimeout(changeTitle, blinkPeriod);
- };
- SJ.copy(scope, {
- isWindowOpen: isWindowOpen,
- getOpenWindowIds: function () {
- var result = [];
- for (var windowId in openWindows) {
- if (openWindows.hasOwnProperty(windowId)) {
- result.push(windowId);
- }
- }
- return result;
- },
- setFocus: function (windowId) {
- if (windowId) {
- onWindowFocusRequest(thisWindowId);
- } else {
- SJ.iwc.EventBus.fire('windowfocusrequest', windowId);
- }
- },
- getThisWindowId: function () {
- return thisWindowId;
- },
- isReady: function () {
- return isWindowMonitorReady;
- },
- onReady: function (fn, scope) {
- if (isWindowMonitorReady) {
- fn.call(scope);
- } else {
- observable.once('windowsmanagerready', fn, scope);
- }
- },
- onWindowsChanged: function (fn, scope) {
- observable.on('windowschanged', fn, scope);
- },
- onceWindowsChanged: function (fn, scope) {
- observable.once('windowschanged', fn, scope);
- },
- unsubscribe: function (fn, scope) {
- observable.un('windowschanged', fn, scope);
- }
- });
- })(SJ.ns('iwc.WindowMonitor'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var lockIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_LOCK_';
- var lockCheckInterval = 500;
- var activeLocks = [];
- var isInitialized = false;
- SJ.localStorage.onChanged(onStorageChanged);
- var observable = new SJ.utils.Observable();
- function on(fn) {
- observable.on('storagechanged', fn);
- };
- function un(fn) {
- observable.un('storagechanged', fn);
- };
- function fire() {
- observable.fire('storagechanged');
- };
- SJ.windowOn('unload', onWindowUnload);
- clearJunkLocks(function () {
- SJ.iwc.WindowMonitor.onWindowsChanged(function (newWindows, removedWindows) {
- if (removedWindows.length) {
- clearJunkLocks();
- }
- });
- setLocksInitialized();
- });
- function onLocksInitialized (fn) {
- observable.once('locksinitialized', fn);
- };
- function setLocksInitialized() {
- isInitialized = true;
- observable.fire('locksinitialized');
- };
- function captureLock(lockId, callback) {
- var captured = false;
- var released = false;
- var listening = false;
- var listeningTimer = null;
- var lockObject = {
- lockId: lockId,
- release: function () {
- released = true;
- if (listening) {
- un(lock);
- listening = false;
- }
- if (captured) {
- captured = false;
- removeLock(lockId);
- }
- },
- isCaptured: function () {
- return captured;
- },
- isReleased: function () {
- return released;
- }
- };
- var listen = function () {
- if (!listening) {
- on(tryCapture);
- listeningTimer = window.setInterval(tryCapture, lockCheckInterval);
- listening = true;
- }
- };
- var stopListening = function () {
- if (listening) {
- un(tryCapture);
- window.clearInterval(listeningTimer);
- listening = false;
- }
- };
- var tryCapture = function () {
- if (captured || released) return;
- if (isLockAlreadyCaptured(lockId)) {
- listen();
- } else {
- stopListening();
- SJ.iwc.Lock.interlockedCall(lockId, function () {
- if (!isLockAlreadyCaptured(lockId)) {
- setLock(lockObject);
- captured = true;
- callback();
- } else {
- listen();
- //Check the lock one more time - possibly the lock was released by closing of owner window during subscription
- if (!isLockAlreadyCaptured(lockId)) {
- stopListening();
- setLock(lockObject);
- captured = true;
- callback();
- }
- }
- });
- }
- };
- if (!isInitialized) {
- onLocksInitialized(tryCapture);
- } else {
- tryCapture();
- }
- return lockObject;
- };
- function onStorageChanged (event) {
- if (event.key) {
- var valueIsRemoved = !event.newValue && !!event.oldValue;//lock functionality needs to know only about removing of items in localStorage
- if (valueIsRemoved && (event.key.substr(0, lockIdPrefix.length) === lockIdPrefix)) {//checks that event is related to locks
- fire();
- }
- } else {
- fire();//For IE8. IE8 doesn't provide any details about storage changes
- }
- };
- function clearJunkLocks (callback) {
- SJ.iwc.WindowMonitor.onReady(function () {
- SJ.localStorage.forEach(function (itemKey, itemValue) {
- if (itemValue && (itemKey.substr(0, lockIdPrefix.length) === lockIdPrefix)) {
- var lockId = itemKey.substr(lockIdPrefix.length);
- if (isJunkLock(lockId, itemValue)) {
- SJ.iwc.Lock.interlockedCall(lockId, function () {
- var serializedLock = SJ.localStorage.getItem(itemKey);
- if (serializedLock && isJunkLock(lockId, serializedLock)) {
- SJ.localStorage.removeItem(itemKey);
- fire();
- }
- });
- }
- }
- });
- if (callback) {
- callback();
- }
- });
- };
- function isJunkLock(lockId, serializedLock) {
- var lockInfo = JSON.parse(serializedLock);
- if (!lockInfo || !lockInfo.timestamp || !lockInfo.ownerWindowId) {//junk lock info
- return true;
- }
- var lockBelongsToThisWindow = lockInfo.ownerWindowId === SJ.iwc.WindowMonitor.getThisWindowId();
- var lockBelongsToClosedWindow = !SJ.iwc.WindowMonitor.isWindowOpen(lockInfo.ownerWindowId);
- return lockBelongsToClosedWindow || (lockBelongsToThisWindow && (findLock(lockId) === -1));
- };
- function releaseAllLocks() {
- var locks = [].concat(activeLocks);
- for (var i = 0; i < locks.length; i++) {
- locks[i].release();
- }
- };
- function onWindowUnload() {
- releaseAllLocks();
- };
- function setLock (lockObject) {
- var now = (new Date()).getTime();
- var lockInfo = {
- timestamp: now,
- ownerWindowId: SJ.iwc.WindowMonitor.getThisWindowId()
- };
- var lockCellId = lockIdPrefix + lockObject.lockId;
- SJ.localStorage.setItem(lockCellId, JSON.stringify(lockInfo));
- activeLocks.push(lockObject);
- };
- function removeLock (lockId) {
- var foundIndex = findLock(lockId);
- if (foundIndex !== -1) {
- activeLocks.splice(foundIndex, 1);
- }
- var lockCellId = lockIdPrefix + lockId;
- var serializedData = SJ.localStorage.getItem(lockCellId);
- if (serializedData) {
- var lockInfo = JSON.parse(serializedData);
- if (SJ.iwc.WindowMonitor.getThisWindowId() === lockInfo.ownerWindowId) {
- SJ.localStorage.removeItem(lockCellId);
- }
- }
- fire();
- };
- function isLockAlreadyCaptured (lockId) {
- var lockCellId = lockIdPrefix + lockId;
- var serializedData = SJ.localStorage.getItem(lockCellId);
- if (serializedData) {
- var lockInfo = JSON.parse(serializedData);
- return SJ.iwc.WindowMonitor.isWindowOpen(lockInfo.ownerWindowId);
- }
- return false;
- };
- function findLock(lockId) {
- for (var i = 0; i < activeLocks.length; i++) {
- if (activeLocks[i].lockId === lockId) {
- return i;
- }
- }
- return -1;
- };
- scope.capture = captureLock;
- SJ.lock = captureLock;
- })(SJ.ns('iwc.Lock'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var Client = function(serverId, readyCallback) {
- var me = this;
- me._serverId = serverId;
- me._isReady = false;
- me._observable = SJ.utils.Observable.decorate(me, true);
- me._serverDescriptionHolder = new SJ.iwc.SharedData(serverId);
- var serverDescription = me._serverDescriptionHolder.get();
- if (serverDescription) {
- me.updateContract(serverDescription);
- }
- me._serverDescriptionHolder.onChanged(function (newServerDescription) {
- me.updateContract(newServerDescription);
- });
- if (readyCallback) {
- me.onReady(readyCallback);
- }
- };
- Client.prototype = {
- constructor: Client,
- onReady: function(fn, scope) {
- var me = this;
- if (me._isReady) {
- fn.call(scope);
- } else {
- me._observable.once('ready', fn, scope);
- }
- },
- //region Private
- updateContract: function(serverDescription) {
- var me = this;
- var serverMethods = serverDescription;
- serverMethods.forEach(function(methodName) {
- if (!me[methodName]) {
- me[methodName] = me.createProxyMethod(methodName);
- }
- });
- if (!me._isReady) {
- me._isReady = true;
- me._observable.fire('ready');
- }
- },
- createProxyMethod: function(methodName) {
- var me = this;
- return function() {
- var callId = null;
- var callback = null;
- var args = Array.prototype.slice.call(arguments, 0);
- if (args.length && SJ.isFunction(args[args.length - 1])) {
- callId = SJ.generateGUID();
- callback = args.pop();
- }
- var eventData = {
- methodName: methodName,
- callId: callId,
- args: args
- };
- SJ.iwc.EventBus.fire('servercall_' + me._serverId, eventData);
- if (callId) {
- SJ.iwc.EventBus.once('servercallback_' + callId, callback);
- }
- };
- }
- //endregion
- };
- scope.Client = Client;
- })(SJ.ns('iwc'));
- //https://github.com/slimjack/IWC
- (function (scope) {
- var Server = function(serverId, config) {
- var me = this;
- me._serverId = serverId;
- me._serverDescriptionHolder = new SJ.iwc.SharedData(me._serverId);
- SJ.copy(me, config.exposed);
- var exposedConfig = config.exposed;
- delete config.exposed;
- SJ.copy(me, config);
- SJ.lock(serverId, function () {
- me.onInit();
- me.updateServerDescription(exposedConfig);
- SJ.iwc.EventBus.on('servercall_' + me._serverId, me.onServerCall, me);
- });
- };
- Server.prototype = {
- constructor: Server,
- onInit: SJ.emptyFn,
- //region Private
- updateServerDescription: function (exposedConfig) {
- var me = this;
- var serverMethods = [];
- SJ.Object.each(exposedConfig, function (methodName) {
- serverMethods.push(methodName);
- });
- me._serverDescriptionHolder.set(serverMethods);
- },
- onServerCall: function (eventData) {
- var me = this;
- var args = eventData.args || [];
- if (eventData.callId) {
- var callback = function() {
- var callbackArgs = Array.prototype.slice.call(arguments, 0);
- callbackArgs.unshift('servercallback_' + callId);
- SJ.iwc.EventBus.fire.apply(SJ.iwc.EventBus, callbackArgs);
- };
- args.unshift(callback);
- } else {
- //empty callback
- args.unshift(SJ.emptyFn);
- }
- me[eventData.methodName].apply(me, args);
- }
- //endregion
- };
- scope.Server = Server;
- })(SJ.ns('iwc'));