IWC

Inter-window (cross-tab) communication library.

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/405935/819599/IWC.js

  1. //https://github.com/slimjack/IWC
  2. var SJ = SJ || {};
  3. SJ.ns = function createNameSpace(namespace) {
  4. var nsparts = namespace.split(".");
  5. var parent = SJ;
  6. if (nsparts[0] === "SJ") {
  7. nsparts = nsparts.slice(1);
  8. }
  9. for (var i = 0; i < nsparts.length; i++) {
  10. var partname = nsparts[i];
  11. if (typeof parent[partname] === "undefined") {
  12. parent[partname] = {};
  13. }
  14. parent = parent[partname];
  15. }
  16. return parent;
  17. };
  18.  
  19. //https://github.com/slimjack/IWC
  20. (function (scope) {
  21. var fixedHandlers = {};
  22. var handlerId = 0;
  23. var toString = ({}).toString;
  24. var basicUtils = {
  25. appName: window.applicationName || 'DEFAULT',
  26. generateGUID: function () {
  27. var d = new Date().getTime();
  28. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
  29. var r = (d + Math.random() * 16) % 16 | 0;
  30. d = Math.floor(d / 16);
  31. return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
  32. });
  33. return uuid;
  34. },
  35.  
  36. callback: function (callback) {
  37. if (SJ.isFunction(callback)) {
  38. var args = Array.prototype.slice.call(arguments, 1);
  39. callback.apply(window, args);
  40. }
  41. },
  42.  
  43. windowOn: function (eventName, handler) {
  44. handler.handlerId = ++handlerId;
  45. var fixedHandler = function (event) {
  46. event = event || window.event;
  47. handler(event);
  48. };
  49. fixedHandlers[handler.handlerId] = handler;
  50. if (window.addEventListener) {
  51. window.addEventListener(eventName, fixedHandler, false);
  52. } else if (window.attachEvent) {
  53. window.attachEvent('on' + eventName, fixedHandler);
  54. }
  55. },
  56.  
  57. windowUn: function (eventName, handler) {
  58. if (window.removeEventListener) {
  59. window.removeEventListener(eventName, fixedHandlers[handler.handlerId], false);
  60. } else if (window.detachEvent) {
  61. window.detachEvent('un' + eventName, fixedHandlers[handler.handlerId]);
  62. }
  63. delete fixedHandlers[handler.handlerId];
  64. },
  65.  
  66. isIE: function () {
  67. var isIE11 = (!(window.ActiveXObject) && ('ActiveXObject' in window));
  68. if (isIE11) {
  69. return 11;
  70. }
  71. var myNav = navigator.userAgent.toLowerCase();
  72. return (myNav.indexOf('msie') !== -1) ? parseInt(myNav.split('msie')[1]) : false;
  73. },
  74.  
  75. copy: function (dst, src) {
  76. var i;
  77. for (i in src) {
  78. dst[i] = src[i];
  79. }
  80.  
  81. return dst;
  82. },
  83.  
  84. emptyFn: function() {},
  85.  
  86. isEmpty: function (value) {
  87. return (value == null) || value === '' || (SJ.isArray(value) && value.length === 0);
  88. },
  89.  
  90. isArray: ('isArray' in Array) ? Array.isArray : function (value) {
  91. return toString.call(value) === '[object Array]';
  92. },
  93.  
  94. isDate: function (value) {
  95. return toString.call(value) === '[object Date]';
  96. },
  97.  
  98. isObject: function (value) {
  99. return value !== null && value !== undefined && toString.call(value) === '[object Object]';
  100. },
  101.  
  102. isPrimitive: function (value) {
  103. var type = typeof value;
  104.  
  105. return type === 'number' || type === 'string' || type === 'boolean';
  106. },
  107.  
  108. isFunction: function (value) {
  109. return !!value && toString.call(value) === '[object Function]';
  110. },
  111.  
  112. isNumber: function (value) {
  113. return typeof value === 'number' && isFinite(value);
  114. },
  115.  
  116. isNumeric: function (value) {
  117. return !isNaN(parseFloat(value)) && isFinite(value);
  118. },
  119.  
  120. isString: function (value) {
  121. return typeof value === 'string';
  122. },
  123.  
  124. isBoolean: function (value) {
  125. return typeof value === 'boolean';
  126. }
  127. };
  128. basicUtils.copy(scope, basicUtils);
  129. })(SJ);
  130.  
  131. //https://github.com/slimjack/IWC
  132. (function (scope) {
  133. var ObjectHelper = {
  134. each: function (obj, fn) {
  135. for (var propName in obj) {
  136. if (obj.hasOwnProperty(propName)) {
  137. if (fn(obj[propName], propName) === false) {
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. };
  144. SJ.copy(scope, ObjectHelper);
  145. })(SJ.ns('Object'));
  146.  
  147. //https://github.com/slimjack/IWC
  148. (function (scope) {
  149. function decorate(target) {
  150. var listeners = {};
  151.  
  152. function removeListener(eventName, listener) {
  153. for (var i = 0; i < listeners[eventName].length; i++) {
  154. if (listeners[eventName][i] === listener) {
  155. listeners[eventName].splice(i, 1);
  156. }
  157. }
  158. }
  159.  
  160. function getListener(eventName, listenerFn, scope) {
  161. var eventListeners = listeners[eventName];
  162. if (eventListeners) {
  163. for (var i = 0; i < eventListeners.length; i++) {
  164. if (eventListeners[i].fn === listenerFn && eventListeners[i].scope === scope) {
  165. return eventListeners[i];
  166. }
  167. }
  168. }
  169. return null;
  170. }
  171.  
  172. target.on = function (eventName, listenerFn, scope) {
  173. if (!listeners[eventName]) {
  174. listeners[eventName] = [];
  175. }
  176. if (!getListener(eventName, listenerFn, scope)) {
  177. listeners[eventName].push({
  178. fn: listenerFn,
  179. scope: scope
  180. });
  181. }
  182. };
  183.  
  184. target.once = function (eventName, listenerFn, scope) {
  185. if (!getListener(eventName, listenerFn, scope)) {
  186. listeners[eventName] = listeners[eventName] || [];
  187. listeners[eventName].push({
  188. fn: listenerFn,
  189. scope: scope,
  190. single: true
  191. });
  192. }
  193. };
  194.  
  195. target.un = function (eventName, listenerFn, scope) {
  196. var listener = getListener(eventName, listenerFn, scope);
  197. if (listener) {
  198. removeListener(eventName, listener);
  199. }
  200. };
  201.  
  202. target.fire = function (eventName) {
  203. var eventListeners = listeners[eventName];
  204. if (eventListeners) {
  205. eventListeners = [].concat(eventListeners);
  206. var args = Array.prototype.slice.call(arguments, 1);
  207. for (var i = 0; i < eventListeners.length; i++) {
  208. eventListeners[i].fn.apply(eventListeners[i].scope, args);
  209. if (eventListeners[i].single) {
  210. removeListener(eventName, eventListeners[i]);
  211. }
  212. }
  213. }
  214. };
  215.  
  216. return target;
  217. };
  218.  
  219. function Observable() {
  220. decorate(this);
  221. };
  222. Observable.decorate = function (target, onlyPublic) {
  223. var observable;
  224. if (onlyPublic) {
  225. observable = new Observable();
  226. target.on = observable.on;
  227. target.un = observable.un;
  228. target.once = observable.once;
  229. } else {
  230. observable = decorate(target);
  231. }
  232. return observable;
  233. };
  234.  
  235. scope.Observable = Observable;
  236. })(SJ.ns('utils'));
  237.  
  238. //https://github.com/slimjack/IWC
  239. (function (scope) {
  240. //LocalStorage wrapper is used to abstract from some issues related to different browsers
  241. var originalLocalStorage = window.localStorage;
  242. if (typeof originalLocalStorage === 'undefined') {
  243. originalLocalStorage = {
  244. getItem: function () { },
  245. setItem: function () { },
  246. removeItem: function () { }
  247. };
  248. alert('Local storage is not supported on this browser. Some features will not work.');
  249. }
  250. var isIE11 = SJ.isIE() === 11;
  251. var lastStorageEventKey;
  252. var lastStorageEventValue;
  253. var observableOnlyExternal = new SJ.utils.Observable();
  254. //observable is used only for events from other windows
  255. var observableAll;
  256. //observableAll is used for events from other windows and from this window too
  257. var thisWindowEventsBug = (SJ.isIE() === 10) || (SJ.isIE() === 9);
  258. /*IE9 and IE10 both incorrectly fire the storage event in ALL windows/tabs,
  259. https://connect.microsoft.com/IE/feedback/details/774798/localstorage-event-fired-in-source-window
  260. */
  261. if (!thisWindowEventsBug) {
  262. //if browser works according localStorage specification, this window events will be fired from this localStorage wrapper
  263. observableAll = new SJ.utils.Observable();
  264. };
  265.  
  266.  
  267. var onStorage = isIE11 ?
  268. function (event) {
  269. event = event || window.event;
  270. // Workaround for IE11 (value from storage is not actual in event handler)
  271. lastStorageEventKey = event.key;
  272. lastStorageEventValue = event.newValue;
  273. observableOnlyExternal.fire('storage', event);
  274. observableAll.fire('storage', event);
  275. } : thisWindowEventsBug ?
  276. function (event) {
  277. event = event || window.event;
  278. observableOnlyExternal.fire('storage', event);
  279. } :
  280. function (event) {
  281. event = event || window.event;
  282. observableOnlyExternal.fire('storage', event);
  283. observableAll.fire('storage', event);
  284. };
  285.  
  286. if (window.addEventListener) {
  287. var wnd = isIE11 ? window.top : window; // Workaround for IE11 (for iframes)
  288. wnd.addEventListener('storage', onStorage, false);
  289. if (wnd != window) {
  290. window.addEventListener('unload', function () {
  291. wnd.removeEventListener('storage', onStorage, false);
  292. });
  293. }
  294. } else if (window.attachEvent) {
  295. document.attachEvent('onstorage', onStorage);
  296. }
  297. scope.localStorage = {
  298. onChanged: function (fn, scope, listenThisWindow) {
  299. /*If listenThisWindow is true then event handler will be called for events fired from this window and from other.
  300. Otherwise it will be called only for events from other windows
  301. If browser has an event bug (storage recieved events from this window), then listenThisWindow is ignored, because this behaviour uncontrollable in buggy browser*/
  302. if (listenThisWindow && !thisWindowEventsBug) {
  303. observableAll.on('storage', fn, scope);
  304. } else {
  305. observableOnlyExternal.on('storage', fn, scope);
  306. }
  307. },
  308.  
  309. onceChanged: function (fn, scope, listenThisWindow) {
  310. if (listenThisWindow && !thisWindowEventsBug) {
  311. observableAll.once('storage', fn, scope);
  312. } else {
  313. observableOnlyExternal.once('storage', fn, scope);
  314. }
  315. },
  316.  
  317. unChanged: function (fn, scope) {
  318. observableOnlyExternal.un('storage', fn, scope);
  319. if (!thisWindowEventsBug) {
  320. observableAll.un('storage', fn, scope);
  321. }
  322. },
  323.  
  324. getItem: function (key) {
  325. if (isIE11 && (lastStorageEventKey === key)) {
  326. return lastStorageEventValue;
  327. }
  328. return originalLocalStorage.getItem(key);
  329. },
  330. setItem: thisWindowEventsBug ? function (key, value) {
  331. if (isIE11 && (lastStorageEventKey === key)) {
  332. lastStorageEventValue = value;
  333. }
  334. originalLocalStorage.setItem(key, value);
  335. } : function (key, value) {
  336. var oldValue = this.getItem(key);
  337. if (isIE11 && (lastStorageEventKey === key)) {
  338. lastStorageEventValue = value;
  339. }
  340. originalLocalStorage.setItem(key, value);
  341. var event = {
  342. key: key,
  343. oldValue: oldValue,
  344. newValue: value,
  345. url: window.location.href.toString()
  346. };
  347. observableAll.fire('storage', event);
  348. },
  349. removeItem: thisWindowEventsBug ? function (key) {
  350. if (isIE11 && (lastStorageEventKey === key)) {
  351. lastStorageEventValue = null;
  352. }
  353. originalLocalStorage.removeItem(key);
  354. } : function (key) {
  355. var oldValue = this.getItem(key);
  356. if (isIE11 && (lastStorageEventKey === key)) {
  357. lastStorageEventValue = null;
  358. }
  359. originalLocalStorage.removeItem(key);
  360. var event = {
  361. key: key,
  362. oldValue: oldValue,
  363. newValue: null,
  364. url: window.location.href.toString()
  365. };
  366. observableAll.fire('storage', event);
  367. },
  368.  
  369. forEach: isIE11 ? function (fn) {
  370. for (var i = 0; i < originalLocalStorage.length; i++) {
  371. var itemKey = originalLocalStorage.key(i);
  372. var itemValue;
  373. if (itemKey === lastStorageEventKey) {
  374. itemValue = lastStorageEventValue;
  375. } else {
  376. itemValue = originalLocalStorage.getItem(itemKey);
  377. }
  378. if (fn(itemKey, itemValue) === false) {
  379. break;
  380. }
  381. }
  382. } : function (fn) {
  383. for (var i = 0; i < originalLocalStorage.length; i++) {
  384. var itemKey = originalLocalStorage.key(i);
  385. var itemValue = originalLocalStorage.getItem(itemKey);
  386. if (fn(itemKey, itemValue) === false) {
  387. break;
  388. }
  389. }
  390. },
  391.  
  392. setVersion: function (storagePrefix, version) {
  393. var me = this;
  394. var currentVersion = me.getItem(storagePrefix);
  395. if (currentVersion !== version) {
  396. var itemsToRemove = [];
  397. me.forEach(function (key) {
  398. if (key.substr(0, storagePrefix.length) === storagePrefix) {
  399. itemsToRemove.push(key);
  400. }
  401. });
  402. itemsToRemove.forEach(function (key) {
  403. me.removeItem(key);
  404. });
  405. me.setItem(storagePrefix, version);
  406. }
  407. }
  408. };
  409. })(SJ);
  410. //https://github.com/slimjack/IWC
  411. (function (scope) {
  412. var localStoragePerfix = 'IWC_' + SJ.appName;
  413. scope.getLocalStoragePrefix = function () {
  414. return localStoragePerfix;
  415. };
  416. scope.$version = '0.1.3';
  417. SJ.localStorage.setVersion(localStoragePerfix, scope.$version);
  418. })(SJ.ns('iwc'));
  419. //https://github.com/slimjack/IWC
  420. (function (scope) {
  421. var lockIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_TLOCK_';
  422. var lockTimeout = 3 * 1000;
  423. var lockCheckInterval = 50;
  424. var lockerId = SJ.generateGUID();
  425.  
  426. SJ.localStorage.onChanged(onStorageChanged);
  427.  
  428. var observable = new SJ.utils.Observable();
  429. function on(fn) {
  430. observable.on('storagechanged', fn);
  431. };
  432.  
  433. function un(fn) {
  434. observable.un('storagechanged', fn);
  435. };
  436.  
  437. function fire() {
  438. observable.fire('storagechanged');
  439. };
  440.  
  441. function interlockedCall(lockId, fn) {
  442. var executed = false;
  443. var listening = false;
  444. var listeningTimer = null;
  445. //if testingMode set, lockTimeout may be defined from outside and lock is not released automatically after fn execution
  446. var _lockTimeout = scope.testingMode ? scope.lockTimeout || lockTimeout : lockTimeout;
  447.  
  448. var listen = function () {
  449. if (!listening) {
  450. on(tryLock);
  451. listeningTimer = window.setInterval(tryLock, lockCheckInterval);
  452. listening = true;
  453. }
  454. };
  455. var stopListening = function () {
  456. if (listening) {
  457. un(tryLock);
  458. window.clearInterval(listeningTimer);
  459. listening = false;
  460. }
  461. };
  462. var tryLock = function () {
  463. if (executed) return;
  464.  
  465. var now = (new Date()).getTime();
  466. //begin critical section =============================================
  467. var activeLock = getLock(lockId);
  468. if (activeLock && now - activeLock.timestamp < _lockTimeout) {
  469. listen();
  470. return;
  471. }
  472. executed = true;
  473. stopListening();
  474. setLock(lockId, now);
  475. //end critical section================================================
  476. //Wait for some time to be sure that during critical section the lock was not intercepted. And then continue lock flow
  477. var deferredTimer = window.setTimeout(function () {
  478. window.clearTimeout(deferredTimer);
  479. var activeLock = getLock(lockId);
  480. if (!activeLock || (activeLock.timestamp === now && activeLock.lockerId === lockerId)) {
  481. //The lock is successfully captured
  482. stopListening();
  483. if (!activeLock) {
  484. setLock(lockId, now);
  485. }
  486. fn();
  487. if (!scope.testingMode) {
  488. removeLock(lockId);
  489. }
  490. } else {
  491. //The lock was intercepted
  492. executed = false;
  493. listen();
  494. }
  495. }, 10);//This timeout must be bigger than execution time of critical section.
  496. //For modern computers critical section execution time is less than 1 ms
  497. };
  498.  
  499. tryLock();
  500. };
  501.  
  502. function getLock (lockId) {
  503. var lockStorageId = lockIdPrefix + lockId;
  504. var serializedLock = SJ.localStorage.getItem(lockStorageId);
  505. var result = null;
  506. if (serializedLock) {
  507. var parts = serializedLock.split('.');
  508. result = {
  509. timestamp: parseInt(parts[0]) || 0,
  510. lockerId: parts[1]
  511. };
  512. }
  513. return result;
  514. };
  515.  
  516. function setLock (lockId, timestamp) {
  517. var lockStorageId = lockIdPrefix + lockId;
  518. SJ.localStorage.setItem(lockStorageId, timestamp + '.' + lockerId);
  519. };
  520.  
  521. function removeLock (lockId) {
  522. var lockStorageId = lockIdPrefix + lockId;
  523. SJ.localStorage.removeItem(lockStorageId);
  524. };
  525.  
  526. function onStorageChanged (event) {
  527. if (event.key) {
  528. var valueIsRemoved = !event.newValue && !!event.oldValue;//lock functionality needs to know only about removing of items in localStorage
  529. if (valueIsRemoved && (event.key.substr(0, lockIdPrefix.length) === lockIdPrefix)) {//check that event is related to locks
  530. fire();
  531. }
  532. } else {
  533. fire();//For IE8. IE8 doesn't provide any details about storage changes
  534. }
  535. };
  536.  
  537. scope.interlockedCall = interlockedCall;
  538. SJ.interlockedCall = interlockedCall;
  539. })(SJ.ns('iwc.Lock'));
  540. //https://github.com/slimjack/IWC
  541. (function (scope) {
  542.  
  543. var SharedData = function (dataId) {
  544. var me = this;
  545. me._dataId = dataId;
  546. me._observable = new SJ.utils.Observable();
  547. me._serializedData = SJ.localStorage.getItem(me._dataId);
  548. SJ.localStorage.onChanged(me.onStorageChanged, me, true);
  549. };
  550.  
  551. SharedData.prototype = {
  552. constructor: SharedData,
  553.  
  554. get: function () {
  555. var me = this;
  556. me._serializedData = SJ.localStorage.getItem(me._dataId);
  557. var data = null;
  558. if (me._serializedData) {
  559. data = JSON.parse(me._serializedData);
  560. }
  561. return data;
  562. },
  563.  
  564. set: function (value, callback) {
  565. var me = this;
  566. SJ.iwc.Lock.interlockedCall(me._dataId, function () {
  567. me.writeToStorage(value);
  568. SJ.callback(callback);
  569. });
  570. },
  571.  
  572. change: function (delegate) {
  573. var me = this;
  574. SJ.iwc.Lock.interlockedCall(me._dataId, function () {
  575. var data = me.get();
  576. data = delegate(data);
  577. me.writeToStorage(data);
  578. });
  579. },
  580.  
  581. onChanged: function (fn, scope) {
  582. var me = this;
  583. me._observable.on('changed', fn, scope);
  584. },
  585.  
  586. onceChanged: function (fn, scope) {
  587. var me = this;
  588. me._observable.once('changed', fn, scope);
  589. },
  590.  
  591. unsubscribe: function (fn, scope) {
  592. var me = this;
  593. me._observable.un('changed', fn, scope);
  594. },
  595.  
  596. //region Private
  597. writeToStorage: function (data) {
  598. var me = this;
  599. var serializedData = data !== null ? JSON.stringify(data) : '';
  600. SJ.localStorage.setItem(me._dataId, serializedData);
  601. },
  602.  
  603. onStorageChanged: function (event) {
  604. var me = this;
  605. if (!event.key || (event.key === me._dataId)) {
  606. var serializedData = SJ.localStorage.getItem(me._dataId);
  607. if (serializedData !== me._serializedData) {
  608. me._serializedData = serializedData;
  609. var data = null;
  610. if (serializedData) {
  611. data = JSON.parse(serializedData);
  612. }
  613. me._observable.fire('changed', data);
  614. }
  615. }
  616. }
  617. //endregion
  618. };
  619. scope.SharedData = SharedData;
  620. })(SJ.ns('iwc'));
  621. //https://github.com/slimjack/IWC
  622. (function (scope) {
  623. var busNodeId = SJ.generateGUID();
  624. var observableOnlyExternal = new SJ.utils.Observable();
  625. var observableAll = new SJ.utils.Observable();//for subscribers which listen for all events including genereted from this window
  626. var storageId = SJ.iwc.getLocalStoragePrefix() + '_EBUS';
  627.  
  628. function onStorageChanged (event) {
  629. if ((event.key === storageId) && event.newValue) {
  630. var busEvent = JSON.parse(event.newValue);
  631. if (busEvent.senderBusNodeId !== busNodeId) {
  632. observableOnlyExternal.fire.apply(window, busEvent.args);
  633. observableAll.fire.apply(window, busEvent.args);
  634. }
  635. }
  636. };
  637.  
  638. function fire () {
  639. var busEvent = {
  640. senderBusNodeId: busNodeId,
  641. args: Array.prototype.slice.call(arguments, 0)
  642. };
  643. var serializedBusEvent = JSON.stringify(busEvent);
  644. SJ.localStorage.setItem(storageId, serializedBusEvent);
  645. observableAll.fire.apply(window, busEvent.args);
  646. };
  647.  
  648. SJ.localStorage.onChanged(onStorageChanged);
  649. SJ.copy(scope, {
  650. on: function (eventName, fn, scope, listenThisWindow) {
  651. if (listenThisWindow) {
  652. observableAll.on(eventName, fn, scope);
  653. } else {
  654. observableOnlyExternal.on(eventName, fn, scope);
  655. }
  656. },
  657.  
  658. once: function (eventName, fn, scope, listenThisWindow) {
  659. if (listenThisWindow) {
  660. observableAll.once(eventName, fn, scope);
  661. } else {
  662. observableOnlyExternal.once(eventName, fn, scope);
  663. }
  664. },
  665.  
  666. un: function (eventName, fn, scope, listenThisWindow) {
  667. if (listenThisWindow) {
  668. observableAll.un(eventName, fn, scope);
  669. } else {
  670. observableOnlyExternal.un(eventName, fn, scope);
  671. }
  672. },
  673.  
  674. fire: fire
  675. });
  676. })(SJ.ns('iwc.EventBus'));
  677. //https://github.com/slimjack/IWC
  678. (function (scope) {
  679. var windowRecordLifeTime = 5000;
  680. var obsoleteRequestTimeFrame = 2000;
  681. var openWindows = {};
  682. var obsoleteWindows = {};
  683. var thisWindowId = window.name || SJ.generateGUID();
  684. var isWindowMonitorReady = false;
  685. var observable = new SJ.utils.Observable();
  686. var storageIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_WND_';
  687.  
  688. var windowRecordUpdatingPeriod = windowRecordLifeTime / 2;
  689. var storageId = storageIdPrefix + thisWindowId;
  690.  
  691. SJ.windowOn('unload', onWindowUnload);
  692. SJ.localStorage.onChanged(onStorageChanged);
  693. SJ.iwc.EventBus.on('windowfocusrequest', onWindowFocusRequest);
  694. SJ.iwc.EventBus.on('windowisaliverequest', onWindowIsAliveRequest);
  695. SJ.iwc.EventBus.on('windowisaliveresponce', onWindowIsAliveResponce);
  696.  
  697. updateDataInStorage();
  698. window.setInterval(updateDataInStorage, windowRecordUpdatingPeriod);
  699.  
  700. function onWindowUnload() {
  701. SJ.localStorage.removeItem(storageId);
  702. };
  703.  
  704. function onWindowIsAliveRequest(windowId) {
  705. if (windowId === thisWindowId) {
  706. updateDataInStorage();
  707. SJ.iwc.EventBus.fire('windowisaliveresponce', windowId);
  708. }
  709. };
  710.  
  711. function onWindowIsAliveResponce(windowId) {
  712. if ((windowId !== thisWindowId) && obsoleteWindows[windowId]) {
  713. delete obsoleteWindows[windowId];
  714. }
  715. };
  716.  
  717. function onStorageChanged(event) {
  718. if (event.key) {
  719. if (event.key.substr(0, storageIdPrefix.length) === storageIdPrefix) {//check that event is related to WindowsManager
  720. updateDataFromStorage();
  721. }
  722. } else {
  723. updateDataFromStorage();//For IE8. IE8 doesn't provide any details about storage changes
  724. }
  725. };
  726.  
  727. function getOpenWindows() {
  728. var windows = {};
  729. SJ.localStorage.forEach(function (itemKey, itemValue) {
  730. if (itemValue && (itemKey.substr(0, storageIdPrefix.length) === storageIdPrefix)) {
  731. var windowId = itemKey.substr(storageIdPrefix.length);
  732. windows[windowId] = parseInt(itemValue);
  733. }
  734. });
  735. removeObsoleteRecords(windows);
  736. return windows;
  737. };
  738.  
  739. function updateDataInStorage() {
  740. updateDataFromStorage();
  741. var now = (new Date()).getTime();
  742. openWindows[thisWindowId] = now;
  743. SJ.localStorage.setItem(storageId, now);
  744. if (!isWindowMonitorReady) {
  745. isWindowMonitorReady = true;
  746. observable.fire('windowsmanagerready');
  747. }
  748. };
  749.  
  750. function removeObsoleteRecords(windows) {
  751. var now = (new Date()).getTime();
  752. for (var windowId in windows) {
  753. if (windows.hasOwnProperty(windowId) && windowId !== thisWindowId) {
  754. var recordAge = now - windows[windowId];
  755. if ((recordAge > 2 * windowRecordLifeTime)
  756. || ((recordAge > windowRecordLifeTime) && obsoleteWindows[windowId] && (now - obsoleteWindows[windowId] > obsoleteRequestTimeFrame))) {
  757. delete windows[windowId];
  758. SJ.localStorage.removeItem(storageIdPrefix + windowId);
  759. } else if (recordAge > windowRecordLifeTime) {
  760. if (!obsoleteWindows[windowId]) {
  761. obsoleteWindows[windowId] = now;
  762. SJ.iwc.EventBus.fire('windowisaliverequest', windowId);
  763. }
  764. } else if (obsoleteWindows[windowId]) {
  765. delete obsoleteWindows[windowId];
  766. }
  767. }
  768. }
  769. for (var windowId in obsoleteWindows) {
  770. if (obsoleteWindows.hasOwnProperty(windowId) && !windows.hasOwnProperty(windowId)) {
  771. delete obsoleteWindows[windowId];
  772. }
  773. }
  774. };
  775.  
  776. function isWindowOpen(windowId) {
  777. return !!openWindows[windowId];
  778. };
  779.  
  780. function updateDataFromStorage() {
  781. var newOpenWindows = getOpenWindows();
  782. var newWindows = [];
  783. for (var windowId in newOpenWindows) {
  784. if (newOpenWindows.hasOwnProperty(windowId) && !openWindows[windowId]) {
  785. newWindows.push(windowId);
  786. }
  787. }
  788. var closedWindows = [];
  789. for (var windowId in openWindows) {
  790. if (openWindows.hasOwnProperty(windowId) && !newOpenWindows[windowId]) {
  791. closedWindows.push(windowId);
  792. }
  793. }
  794. openWindows = newOpenWindows;
  795. if (newWindows.length || closedWindows.length) {
  796. onWindowsChanged(newWindows, closedWindows);
  797. }
  798. };
  799.  
  800. function onWindowsChanged(newWindows, closedWindows) {
  801. observable.fire('windowschanged', newWindows, closedWindows);
  802. };
  803.  
  804. function onWindowFocusRequest (windowId) {
  805. if (windowId === thisWindowId) {
  806. window.focus();
  807. blinkTitle();
  808. }
  809. };
  810.  
  811. var blinkCounter = 0;
  812. function blinkTitle() {
  813. if (blinkCounter) {
  814. return;
  815. }
  816.  
  817. var blinkPeriod = 500;//ms
  818. var numOfBlinks = 3;
  819. var title = document.title;
  820. var isTitleVisible = false;
  821. blinkCounter = numOfBlinks * 2;
  822.  
  823. var changeTitle = function () {
  824. if (isTitleVisible) {
  825. document.title = title;
  826. } else {
  827. document.title = "******";
  828. }
  829. isTitleVisible = !isTitleVisible;
  830. blinkCounter--;
  831. if (blinkCounter) {
  832. window.setTimeout(changeTitle, blinkPeriod);
  833. }
  834. };
  835. window.setTimeout(changeTitle, blinkPeriod);
  836. };
  837.  
  838. SJ.copy(scope, {
  839. isWindowOpen: isWindowOpen,
  840.  
  841. getOpenWindowIds: function () {
  842. var result = [];
  843. for (var windowId in openWindows) {
  844. if (openWindows.hasOwnProperty(windowId)) {
  845. result.push(windowId);
  846. }
  847. }
  848. return result;
  849. },
  850.  
  851. setFocus: function (windowId) {
  852. if (windowId) {
  853. onWindowFocusRequest(thisWindowId);
  854. } else {
  855. SJ.iwc.EventBus.fire('windowfocusrequest', windowId);
  856. }
  857. },
  858.  
  859. getThisWindowId: function () {
  860. return thisWindowId;
  861. },
  862.  
  863. isReady: function () {
  864. return isWindowMonitorReady;
  865. },
  866.  
  867. onReady: function (fn, scope) {
  868. if (isWindowMonitorReady) {
  869. fn.call(scope);
  870. } else {
  871. observable.once('windowsmanagerready', fn, scope);
  872. }
  873. },
  874.  
  875. onWindowsChanged: function (fn, scope) {
  876. observable.on('windowschanged', fn, scope);
  877. },
  878.  
  879. onceWindowsChanged: function (fn, scope) {
  880. observable.once('windowschanged', fn, scope);
  881. },
  882.  
  883. unsubscribe: function (fn, scope) {
  884. observable.un('windowschanged', fn, scope);
  885. }
  886. });
  887. })(SJ.ns('iwc.WindowMonitor'));
  888.  
  889. //https://github.com/slimjack/IWC
  890. (function (scope) {
  891. var lockIdPrefix = SJ.iwc.getLocalStoragePrefix() + '_LOCK_';
  892. var lockCheckInterval = 500;
  893. var activeLocks = [];
  894. var isInitialized = false;
  895.  
  896. SJ.localStorage.onChanged(onStorageChanged);
  897.  
  898. var observable = new SJ.utils.Observable();
  899.  
  900. function on(fn) {
  901. observable.on('storagechanged', fn);
  902. };
  903.  
  904. function un(fn) {
  905. observable.un('storagechanged', fn);
  906. };
  907.  
  908. function fire() {
  909. observable.fire('storagechanged');
  910. };
  911.  
  912. SJ.windowOn('unload', onWindowUnload);
  913. clearJunkLocks(function () {
  914. SJ.iwc.WindowMonitor.onWindowsChanged(function (newWindows, removedWindows) {
  915. if (removedWindows.length) {
  916. clearJunkLocks();
  917. }
  918. });
  919. setLocksInitialized();
  920. });
  921.  
  922. function onLocksInitialized (fn) {
  923. observable.once('locksinitialized', fn);
  924. };
  925.  
  926. function setLocksInitialized() {
  927. isInitialized = true;
  928. observable.fire('locksinitialized');
  929. };
  930.  
  931. function captureLock(lockId, callback) {
  932. var captured = false;
  933. var released = false;
  934. var listening = false;
  935. var listeningTimer = null;
  936. var lockObject = {
  937. lockId: lockId,
  938. release: function () {
  939. released = true;
  940. if (listening) {
  941. un(lock);
  942. listening = false;
  943. }
  944. if (captured) {
  945. captured = false;
  946. removeLock(lockId);
  947. }
  948. },
  949. isCaptured: function () {
  950. return captured;
  951. },
  952. isReleased: function () {
  953. return released;
  954. }
  955. };
  956.  
  957. var listen = function () {
  958. if (!listening) {
  959. on(tryCapture);
  960. listeningTimer = window.setInterval(tryCapture, lockCheckInterval);
  961. listening = true;
  962. }
  963. };
  964. var stopListening = function () {
  965. if (listening) {
  966. un(tryCapture);
  967. window.clearInterval(listeningTimer);
  968. listening = false;
  969. }
  970. };
  971. var tryCapture = function () {
  972. if (captured || released) return;
  973. if (isLockAlreadyCaptured(lockId)) {
  974. listen();
  975. } else {
  976. stopListening();
  977. SJ.iwc.Lock.interlockedCall(lockId, function () {
  978. if (!isLockAlreadyCaptured(lockId)) {
  979. setLock(lockObject);
  980. captured = true;
  981. callback();
  982. } else {
  983. listen();
  984. //Check the lock one more time - possibly the lock was released by closing of owner window during subscription
  985. if (!isLockAlreadyCaptured(lockId)) {
  986. stopListening();
  987. setLock(lockObject);
  988. captured = true;
  989. callback();
  990. }
  991. }
  992. });
  993. }
  994. };
  995. if (!isInitialized) {
  996. onLocksInitialized(tryCapture);
  997. } else {
  998. tryCapture();
  999. }
  1000. return lockObject;
  1001. };
  1002.  
  1003. function onStorageChanged (event) {
  1004. if (event.key) {
  1005. var valueIsRemoved = !event.newValue && !!event.oldValue;//lock functionality needs to know only about removing of items in localStorage
  1006. if (valueIsRemoved && (event.key.substr(0, lockIdPrefix.length) === lockIdPrefix)) {//checks that event is related to locks
  1007. fire();
  1008. }
  1009. } else {
  1010. fire();//For IE8. IE8 doesn't provide any details about storage changes
  1011. }
  1012. };
  1013.  
  1014. function clearJunkLocks (callback) {
  1015. SJ.iwc.WindowMonitor.onReady(function () {
  1016. SJ.localStorage.forEach(function (itemKey, itemValue) {
  1017. if (itemValue && (itemKey.substr(0, lockIdPrefix.length) === lockIdPrefix)) {
  1018. var lockId = itemKey.substr(lockIdPrefix.length);
  1019. if (isJunkLock(lockId, itemValue)) {
  1020. SJ.iwc.Lock.interlockedCall(lockId, function () {
  1021. var serializedLock = SJ.localStorage.getItem(itemKey);
  1022. if (serializedLock && isJunkLock(lockId, serializedLock)) {
  1023. SJ.localStorage.removeItem(itemKey);
  1024. fire();
  1025. }
  1026. });
  1027. }
  1028. }
  1029. });
  1030. if (callback) {
  1031. callback();
  1032. }
  1033. });
  1034. };
  1035.  
  1036. function isJunkLock(lockId, serializedLock) {
  1037. var lockInfo = JSON.parse(serializedLock);
  1038. if (!lockInfo || !lockInfo.timestamp || !lockInfo.ownerWindowId) {//junk lock info
  1039. return true;
  1040. }
  1041. var lockBelongsToThisWindow = lockInfo.ownerWindowId === SJ.iwc.WindowMonitor.getThisWindowId();
  1042. var lockBelongsToClosedWindow = !SJ.iwc.WindowMonitor.isWindowOpen(lockInfo.ownerWindowId);
  1043. return lockBelongsToClosedWindow || (lockBelongsToThisWindow && (findLock(lockId) === -1));
  1044. };
  1045.  
  1046. function releaseAllLocks() {
  1047. var locks = [].concat(activeLocks);
  1048. for (var i = 0; i < locks.length; i++) {
  1049. locks[i].release();
  1050. }
  1051. };
  1052.  
  1053. function onWindowUnload() {
  1054. releaseAllLocks();
  1055. };
  1056.  
  1057.  
  1058. function setLock (lockObject) {
  1059. var now = (new Date()).getTime();
  1060. var lockInfo = {
  1061. timestamp: now,
  1062. ownerWindowId: SJ.iwc.WindowMonitor.getThisWindowId()
  1063. };
  1064. var lockCellId = lockIdPrefix + lockObject.lockId;
  1065. SJ.localStorage.setItem(lockCellId, JSON.stringify(lockInfo));
  1066. activeLocks.push(lockObject);
  1067. };
  1068.  
  1069. function removeLock (lockId) {
  1070. var foundIndex = findLock(lockId);
  1071. if (foundIndex !== -1) {
  1072. activeLocks.splice(foundIndex, 1);
  1073. }
  1074. var lockCellId = lockIdPrefix + lockId;
  1075. var serializedData = SJ.localStorage.getItem(lockCellId);
  1076. if (serializedData) {
  1077. var lockInfo = JSON.parse(serializedData);
  1078. if (SJ.iwc.WindowMonitor.getThisWindowId() === lockInfo.ownerWindowId) {
  1079. SJ.localStorage.removeItem(lockCellId);
  1080. }
  1081. }
  1082. fire();
  1083. };
  1084.  
  1085. function isLockAlreadyCaptured (lockId) {
  1086. var lockCellId = lockIdPrefix + lockId;
  1087. var serializedData = SJ.localStorage.getItem(lockCellId);
  1088. if (serializedData) {
  1089. var lockInfo = JSON.parse(serializedData);
  1090. return SJ.iwc.WindowMonitor.isWindowOpen(lockInfo.ownerWindowId);
  1091. }
  1092. return false;
  1093. };
  1094.  
  1095. function findLock(lockId) {
  1096. for (var i = 0; i < activeLocks.length; i++) {
  1097. if (activeLocks[i].lockId === lockId) {
  1098. return i;
  1099. }
  1100. }
  1101. return -1;
  1102. };
  1103.  
  1104. scope.capture = captureLock;
  1105. SJ.lock = captureLock;
  1106. })(SJ.ns('iwc.Lock'));
  1107.  
  1108. //https://github.com/slimjack/IWC
  1109. (function (scope) {
  1110. var Client = function(serverId, readyCallback) {
  1111. var me = this;
  1112. me._serverId = serverId;
  1113. me._isReady = false;
  1114. me._observable = SJ.utils.Observable.decorate(me, true);
  1115. me._serverDescriptionHolder = new SJ.iwc.SharedData(serverId);
  1116. var serverDescription = me._serverDescriptionHolder.get();
  1117. if (serverDescription) {
  1118. me.updateContract(serverDescription);
  1119. }
  1120. me._serverDescriptionHolder.onChanged(function (newServerDescription) {
  1121. me.updateContract(newServerDescription);
  1122. });
  1123. if (readyCallback) {
  1124. me.onReady(readyCallback);
  1125. }
  1126. };
  1127.  
  1128. Client.prototype = {
  1129. constructor: Client,
  1130.  
  1131. onReady: function(fn, scope) {
  1132. var me = this;
  1133. if (me._isReady) {
  1134. fn.call(scope);
  1135. } else {
  1136. me._observable.once('ready', fn, scope);
  1137. }
  1138. },
  1139.  
  1140. //region Private
  1141. updateContract: function(serverDescription) {
  1142. var me = this;
  1143. var serverMethods = serverDescription;
  1144. serverMethods.forEach(function(methodName) {
  1145. if (!me[methodName]) {
  1146. me[methodName] = me.createProxyMethod(methodName);
  1147. }
  1148. });
  1149. if (!me._isReady) {
  1150. me._isReady = true;
  1151. me._observable.fire('ready');
  1152. }
  1153. },
  1154.  
  1155. createProxyMethod: function(methodName) {
  1156. var me = this;
  1157.  
  1158. return function() {
  1159. var callId = null;
  1160. var callback = null;
  1161. var args = Array.prototype.slice.call(arguments, 0);
  1162. if (args.length && SJ.isFunction(args[args.length - 1])) {
  1163. callId = SJ.generateGUID();
  1164. callback = args.pop();
  1165. }
  1166. var eventData = {
  1167. methodName: methodName,
  1168. callId: callId,
  1169. args: args
  1170. };
  1171. SJ.iwc.EventBus.fire('servercall_' + me._serverId, eventData);
  1172. if (callId) {
  1173. SJ.iwc.EventBus.once('servercallback_' + callId, callback);
  1174. }
  1175. };
  1176. }
  1177. //endregion
  1178. };
  1179. scope.Client = Client;
  1180. })(SJ.ns('iwc'));
  1181.  
  1182. //https://github.com/slimjack/IWC
  1183. (function (scope) {
  1184. var Server = function(serverId, config) {
  1185. var me = this;
  1186. me._serverId = serverId;
  1187. me._serverDescriptionHolder = new SJ.iwc.SharedData(me._serverId);
  1188. SJ.copy(me, config.exposed);
  1189. var exposedConfig = config.exposed;
  1190. delete config.exposed;
  1191. SJ.copy(me, config);
  1192. SJ.lock(serverId, function () {
  1193. me.onInit();
  1194. me.updateServerDescription(exposedConfig);
  1195. SJ.iwc.EventBus.on('servercall_' + me._serverId, me.onServerCall, me);
  1196. });
  1197. };
  1198.  
  1199. Server.prototype = {
  1200. constructor: Server,
  1201.  
  1202. onInit: SJ.emptyFn,
  1203.  
  1204. //region Private
  1205. updateServerDescription: function (exposedConfig) {
  1206. var me = this;
  1207. var serverMethods = [];
  1208. SJ.Object.each(exposedConfig, function (methodName) {
  1209. serverMethods.push(methodName);
  1210. });
  1211. me._serverDescriptionHolder.set(serverMethods);
  1212. },
  1213.  
  1214. onServerCall: function (eventData) {
  1215. var me = this;
  1216. var args = eventData.args || [];
  1217. if (eventData.callId) {
  1218. var callback = function() {
  1219. var callbackArgs = Array.prototype.slice.call(arguments, 0);
  1220. callbackArgs.unshift('servercallback_' + callId);
  1221. SJ.iwc.EventBus.fire.apply(SJ.iwc.EventBus, callbackArgs);
  1222. };
  1223. args.unshift(callback);
  1224. } else {
  1225. //empty callback
  1226. args.unshift(SJ.emptyFn);
  1227. }
  1228. me[eventData.methodName].apply(me, args);
  1229. }
  1230. //endregion
  1231. };
  1232. scope.Server = Server;
  1233. })(SJ.ns('iwc'));