├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── docs ├── api.rst ├── channel_api.rst ├── getting_started.rst └── index.rst ├── package.json ├── public ├── index.html └── lib │ ├── duel.js │ ├── duel.min.js │ └── duel.min.js.map └── test ├── duel_test.js ├── mocha.min.test.html ├── mocha.test.html ├── mocha.test.umd.html ├── phantom.script.js ├── phantom.tab.html └── requirejs.main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm_debug.log 4 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm_debug.log 4 | .DS_Store 5 | .git -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "6.1" 5 | env: 6 | - NODE_ENV="development" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Maslov Ivan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DuelJS v1.2.7 2 | ====== 3 | [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat-square)](http://dueljs.readthedocs.org/) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/studentIvan/dueljs/master/LICENSE) [![Bower](https://img.shields.io/bower/v/duel.svg?style=flat-square)](http://bower.io/search/?q=duel) [![](https://img.shields.io/github/issues-raw/studentIvan/dueljs.svg?style=flat-square)](https://github.com/studentIvan/dueljs/issues/) [![GitHub stars](https://img.shields.io/github/stars/studentIvan/dueljs.svg?style=flat-square)](https://github.com/studentIvan/dueljs/stargazers) [![Travis branch](https://img.shields.io/travis/studentIvan/dueljs/master.svg?style=flat-square)](https://travis-ci.org/studentIvan/dueljs) 4 | 5 | UPD (2025): It's been many years since the last release, I didn't think that this library is still needed by anyone. In the next 2-3 weeks I will update the code and rework everything to the latest current states, thanks :) 6 | 7 | ====================================== 8 | 9 | JavaScript HTML5 Master/Slave Browser Tabs Helper. 10 | 11 | See a brief look on [the homepage](http://dueljs.studentivan.ru) 12 | 13 | Documentation available on http://dueljs.readthedocs.org/ 14 | 15 | ######New in 1.2.7: 16 | * Fixed [#13](https://github.com/studentIvan/dueljs/issues/13): webpack import 17 | 18 | ######New in 1.2.6: 19 | * Fixed [#11](https://github.com/studentIvan/dueljs/issues/11): localstorage issue for private browsing (thanks to Stéphane Bachelier ) 20 | * Library added to npm as dueljs (thanks to Denis Lukov ) 21 | 22 | ######New in 1.2.5: 23 | * Different variables for each channel (setItem, getItem, removeItem) 24 | 25 | ######New in 1.2.4: 26 | * New storage methods setItem, getItem, removeItem with JSONify inside 27 | * Direct localStorage changed to window.localStorage 28 | * Fixed emit bug (arguments) 29 | * New configuration duel.noWarnings 30 | 31 | ```javascript 32 | /** Turn off errors for debug */ 33 | duel.noWarnings = false; 34 | /** New storage methods example */ 35 | var ch = duel.channel('demo'); 36 | ch.setItem('x', 10); 37 | ch.setItem('y', {'a': true}); 38 | ch.getItem('x'); // 10 39 | ch.getItem('y'); // Object {'a': true} 40 | ``` 41 | 42 | ######New in 1.2.3: 43 | * UMD compatible (thanks to RasCarlito ) 44 | * Microsoft Edge attested (thanks to toby11) 45 | 46 | ######New in 1.2.2: 47 | * Fixed some additional bugs (extra-release) 48 | 49 | ######New in 1.2.1: 50 | * Fixed [#5](https://github.com/studentIvan/dueljs/issues/5): localStorage - stack overflow problem (thanks to Alex Core ) 51 | 52 | ######New in 1.2.0: 53 | * New method: channel.off - stop watching event 54 | * New method: channel.once - executing callback only one time and stop watching event 55 | * New method: channel.emit - the alias of channel.broadcast 56 | * Function window.isMaster() now returns true even if no one channel has initialized [#3](https://github.com/studentIvan/dueljs/issues/3) 57 | * Dev test coverage (Mocha + PhantomJS) 58 | 59 | ######New in 1.1.0: 60 | * "storage" event improves performance in modern browsers. 61 | To turn it off and use old method - do: 62 | 63 | ```javascript 64 | duel.useStorageEvent = false; // auto false in IE 65 | ``` 66 | 67 | * Now only slaves can execute triggers 68 | * Some unimportant bug fixes 69 | 70 | ######List of attested browsers: 71 | 72 | 1. Opera 29.0.1795.35 (with storage event) 73 | 2. Chrome 41.0.2272.118 (with storage event) 74 | 3. Firefox 34.0 (with storage event) 75 | 4. Internet Explorer 11 (without storage event) 76 | 5. Safari 534.57.2 (with storage event) 77 | 6. Android 4.3 LT29i default browser (with storage event) 78 | 7. Microsoft Edge 25.10586.0.0 (with storage event) 79 | 80 | Internet Explorer does incorrect. So it using force `useStorageEvent = false` by default. 81 | 82 | ######How it works with Internet Explorer without storage event? 83 | Don't worry. It using setInterval javascript checking. 84 | 85 | ######Testing suite 86 | 1. NodeJS version 6.7.0 87 | 2. PhantomJS version 2.1.1 88 | 3. Chai version 3.5.0 89 | 4. Mocha version 3.1.0 90 | 5. Mocha-PhantomJS custom fork of 4.1.0 91 | 92 | In Mac OS Sierra you want to use phantomjs from brew (brew install phantomjs) and check to link it: 93 | 94 | ``` 95 | $ which phantomjs 96 | /usr/local/bin/phantomjs 97 | ``` 98 | 99 | And also you can create this link by yourself: 100 | 101 | ``` 102 | ln -s /usr/local/Cellar/phantomjs/2.1.1/bin/phantomjs /usr/local/bin/phantomjs 103 | ``` 104 | 105 | The reason I temporary changed original repo of mocha-phantomjs to git+https://github.com/studentIvan/mocha-phantomjs.git#master is cause mocha-phantomjs doesn't support the second phantomjs right now. 106 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "duel", 3 | "version": "1.2.7", 4 | "homepage": "http://dueljs.studentivan.ru", 5 | "authors": [ 6 | "Maslov Ivan " 7 | ], 8 | "description": "JavaScript HTML5 Master/Slave Browser Tabs Helper", 9 | "main": "public/lib/duel.js", 10 | "keywords": [ 11 | "dueljs", 12 | "duel", 13 | "master", 14 | "slave", 15 | "window", 16 | "javascript", 17 | "onfocus", 18 | "broadcasting", 19 | "document.hidden" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | "node_modules", 24 | "bower_components", 25 | "docs", 26 | "test" 27 | ".travis.yml" 28 | ".npmignore" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API:duel 2 | ======== 3 | 4 | duel 5 | ---- 6 | Main global object. 7 | 8 | * global 9 | * type: object 10 | 11 | duel.activeChannels 12 | ------------------- 13 | Collect inside all active channels. 14 | 15 | * public 16 | * type: array 17 | * default: [] 18 | 19 | duel.useStorageEvent 20 | -------------------- 21 | Optional configuration. Storage event improves performance in modern browsers. 22 | 23 | * public 24 | * type: boolean 25 | * default: true (IE - false) 26 | 27 | duel.noWarnings 28 | --------------- 29 | Optional configuration. You can turn it to false for debug. 30 | 31 | * public 32 | * type: boolean 33 | * default: true 34 | 35 | duel.isLocalStorageAvailable() 36 | ------------------------------ 37 | Common function for localStorage detection. 38 | 39 | * public 40 | * type: function 41 | * returns: boolean 42 | 43 | duel.channel(name:string) 44 | ------------------------- 45 | Creates a new channel or join to existed. 46 | Hint: a = duel.channel('x') and b = duel.channel('x') will be linking on ONE object. 47 | 48 | * public 49 | * type: function 50 | * returns: object (duel.DuelAbstractChannel inheritor) 51 | 52 | duel.makeCurrentWindowMaster() 53 | ------------------------------ 54 | Take the all channels in current window and set current window as master for all of them. 55 | 56 | * public 57 | * type: function 58 | 59 | duel.clone(obj:object) 60 | ---------------------- 61 | Common function for copy objects. 62 | 63 | * public 64 | * type: function 65 | * returns: object 66 | * throws error on unsupported type 67 | 68 | duel._generateWindowID() 69 | ------------------------ 70 | Generates, sets up and returns new window/tab ID. 71 | 72 | * private 73 | * type: function 74 | * returns: number 75 | 76 | duel.getWindowID() 77 | ------------------ 78 | Get unique window/tab ID. 79 | 80 | * public 81 | * type: function 82 | * returns: number 83 | 84 | duel.addEvent(el:object, type:string, fn:function) 85 | -------------------------------------------------- 86 | Cross-browser addEvent method. 87 | 88 | * public 89 | * type: function 90 | 91 | duel.storageEvent(event:object) 92 | ------------------------------- 93 | Finds the specific channel and execute event on it. **event** object contains **key:string** and **newValue:string**. 94 | **newValue** is a JSON string, which contains **channelName:string** and **triggerDetails:object**. 95 | **triggerDetails** contains **name:string** and **args:array**. 96 | 97 | * public 98 | * type: function 99 | 100 | duel.DuelAbstractChannel 101 | ------------------------ 102 | Abstract class for possible duel channels. DuelJS probably will support another channels besides **duel.DuelLocalStorageChannel** in future. 103 | 104 | * abstract 105 | * type: function 106 | * returns: object 107 | 108 | duel.DuelLocalStorageChannel 109 | ---------------------------- 110 | Channel class for work with localStorage. 111 | 112 | * abstract 113 | * type: function 114 | * returns: object 115 | 116 | duel.DuelFakeChannel 117 | -------------------- 118 | Channel class for work without localStorage. 119 | 120 | * abstract 121 | * type: function 122 | * returns: object 123 | 124 | window.isMaster() 125 | ----------------- 126 | Take first channel in current window and check is it master or not 127 | 128 | Standard window object spreading method. 129 | Looks like syntax sugar for channelObject.currentWindowIsMaster() 130 | 131 | * public 132 | * type: function 133 | * returns: boolean 134 | 135 | -------------------------------------------------------------------------------- /docs/channel_api.rst: -------------------------------------------------------------------------------- 1 | API:channel object interface 2 | ============================ 3 | 4 | channel.getItem(varName:string) 5 | ------------------------------- 6 | Get jsonify var content from storage. 7 | 8 | * public 9 | * type: function 10 | * returns: mixed 11 | 12 | channel.setItem(varName:string, value:mixed) 13 | -------------------------------------------- 14 | Set storage variable content. 15 | 16 | * public 17 | * type: function 18 | 19 | channel.removeItem(varName:string) 20 | ---------------------------------- 21 | Remove var from storage. 22 | 23 | * public 24 | * type: function 25 | 26 | channel.getName() 27 | ----------------- 28 | Returns the name of this channel. 29 | 30 | * public 31 | * type: function 32 | * returns: string 33 | 34 | channel.setCurrentWindowAsMaster() 35 | ---------------------------------- 36 | Makes current window/tab as master in this channel. 37 | 38 | * public 39 | * type: function 40 | * returns: boolean 41 | 42 | channel.currentWindowIsMaster() 43 | ------------------------------- 44 | Checks the master state of this channel. 45 | 46 | * public 47 | * type: function 48 | * returns: boolean 49 | 50 | channel.broadcast(trigger:string[, arguments:arguments]) 51 | -------------------------------------------------------- 52 | Emits broadcasting. Hint: only master can sends broadcast. 53 | 54 | * public 55 | * type: function 56 | 57 | channel.emit(trigger:string[, arguments:arguments]) 58 | --------------------------------------------------- 59 | Alias of **channel.broadcast** 60 | 61 | channel.executeTrigger(triggerDetails:object[, force:boolean]) 62 | -------------------------------------------------------------- 63 | Executes pointed trigger. **triggerDetails** contains **name:string** and **args:array** 64 | 65 | * public 66 | * type: function 67 | * throws error if triggerDetails is not an instance of Object 68 | 69 | channel.on(trigger:string, callback:function) 70 | --------------------------------------------- 71 | Attaches callback to trigger event. 72 | 73 | * public 74 | * type: function 75 | 76 | channel.once(trigger:string, callback:function) 77 | ----------------------------------------------- 78 | Attaches callback to trigger event only for one time. 79 | 80 | * public 81 | * type: function 82 | 83 | channel.off(trigger:string) 84 | --------------------------- 85 | Detaches callback from trigger event (destroys trigger). 86 | 87 | * public 88 | * type: function 89 | 90 | channel._triggers 91 | ----------------- 92 | Contains triggers of this channel. 93 | 94 | * private 95 | * type: object 96 | 97 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Getting started with DuelJS. 5 | 6 | Requirements 7 | ------------ 8 | 9 | With DuelJS you no need to any requirements - only vanilla js and modern web browser. 10 | Don't try to use it with node.js without browser emulators. 11 | 12 | Installing DuelJS 13 | ----------------- 14 | 15 | You can install it using bower or simple copy duel.js main file into your site or even clone git repository. 16 | 17 | Different ways: 18 | 19 | * Bower package: ``bower install duel --save`` 20 | * Git repo: ``git clone https://github.com/studentIvan/dueljs.git`` 21 | * Main file: `duel.min.js `_ 22 | 23 | 24 | Put it into your webpage: 25 | ```` 26 | 27 | So we've got all the set up out of the way. Let's write some simple code. 28 | :: 29 | 46 | 47 | Broadcasting 48 | ------------ 49 | 50 | When your tab had some channel (e.g. **my_first_channel** from previous section) you can do cross-tab broadcasting. 51 | 52 | Use simple commands: 53 | 54 | * **channel.broadcast('event_name', a,r,g,u,m,e,n,t,s...);** 55 | - send event command to all another tabs in channel. Alias: **channel.emit**. 56 | * **channel.on('event_name', function (a,r,g,u,m,e,n,t,s...) { do here what you want });** 57 | - define watcher for event_name. 58 | * **channel.off('event_name');** 59 | - remove event_name watcher. 60 | * **channel.once('event_name', function (a,r,g,u,m,e,n,t,s...) { do here what you want });** 61 | - define watcher for event_name, do it only one time and remove. 62 | 63 | Articles and examples 64 | --------------------- 65 | 66 | * `Задача коммуникации между вкладками и выявления активной вкладки `_ (russian) 67 | * `Youtube-player integration demonstration `_ (russian) 68 | 69 | If you still need more docs go to `API`_ section 70 | 71 | .. _API: api.html -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | DuelJS Documentation 2 | ==================== 3 | 4 | .. note:: Documentation still in progress. 5 | Welcome to the DuelJS JavaScript library documentation. 6 | 7 | .. _site-docs: 8 | 9 | User Documentation 10 | ------------------ 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | getting_started 16 | api 17 | channel_api 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dueljs", 3 | "version": "1.2.7", 4 | "description": "DuelJS - JavaScript HTML5 Master/Slave Browser Tabs Helper", 5 | "main": "public/lib/duel.js", 6 | "devDependencies": { 7 | "chai": "3.5.0", 8 | "mocha": "3.1.0", 9 | "mocha-phantomjs": "git+https://github.com/studentIvan/mocha-phantomjs.git#master", 10 | "uglifyjs": "*" 11 | }, 12 | "scripts": { 13 | "pretest": "npm install && node_modules/.bin/mocha-phantomjs test/mocha.test.umd.html && node_modules/.bin/mocha-phantomjs test/mocha.test.html && node_modules/.bin/mocha-phantomjs test/mocha.min.test.html", 14 | "test": "phantomjs test/phantom.script.js", 15 | "start": "npm install && http-server -p 8089", 16 | "min": "npm install && node_modules/.bin/uglifyjs --compress --bare-returns --keep-fnames --source-map=public/lib/duel.min.js.map --source-map-url=duel.min.js.map --output=public/lib/duel.min.js --mangle -- public/lib/duel.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/studentIvan/dueljs.git" 21 | }, 22 | "keywords": [ 23 | "dueljs", 24 | "duel", 25 | "master", 26 | "slave", 27 | "window", 28 | "javascript", 29 | "onfocus", 30 | "broadcasting", 31 | "document.hidden" 32 | ], 33 | "author": "Maslov Ivan ", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/studentIvan/dueljs/issues", 37 | "email": "mail@studentivan.ru" 38 | }, 39 | "homepage": "http://dueljs.studentivan.ru" 40 | } 41 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Duel page 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Window ...

13 |
Open new window  14 |
17 |
18 | Hint: open the developer console before broadcasting. 19 |
20 |
21 | 34 | 35 | -------------------------------------------------------------------------------- /public/lib/duel.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * DuelJS JavaScript Library v1.2.7 3 | * https://github.com/studentIvan/dueljs 4 | * http://dueljs.readthedocs.org/en/latest/ 5 | * 6 | * Copyright 2015-2016 Maslov Ivan 7 | * Released under the MIT license 8 | * https://raw.githubusercontent.com/studentIvan/dueljs/master/LICENSE 9 | */ 10 | (function (root, factory) { 11 | 'use strict'; 12 | 13 | if (typeof define === 'function' && define.amd) { 14 | // AMD. Register as an anonymous module. 15 | define(function () { 16 | // Use globals variables in case that they are undefined locally 17 | return factory(root); 18 | }); 19 | } else if (typeof exports === 'object') { 20 | // Note. Does not work with strict CommonJS, but 21 | // only CommonJS-like environments that support module.exports, like Node. 22 | module.exports = factory(root); 23 | } else { 24 | // Browser globals 25 | root.duel = factory(root); 26 | } 27 | }(typeof window !== "undefined" ? window : this, function (window) { 28 | var duel = {}; 29 | 30 | /** 31 | * Optional configuration 32 | * Storage event improves performance in modern browsers 33 | * See list of attested browsers in README.md 34 | * default: true (IE - false) 35 | * @type {boolean} 36 | */ 37 | duel.useStorageEvent = !/trident|MSIE/i.test(navigator.userAgent); 38 | 39 | /** 40 | * Turn off dueljs warnings 41 | * default: true 42 | * @type {boolean} 43 | */ 44 | duel.noWarnings = true; 45 | 46 | /** 47 | * Common function for localStorage detection 48 | * @returns {boolean} 49 | */ 50 | duel.isLocalStorageAvailable = function () { 51 | try { 52 | var hasStorage = typeof localStorage !== 'undefined' && ('setItem' in localStorage) && localStorage.setItem; 53 | if (!hasStorage) { 54 | return false; 55 | } 56 | 57 | var uid = new Date; 58 | localStorage.setItem(uid, uid); 59 | localStorage.removeItem(uid); 60 | return true; 61 | } catch (e) { 62 | return false; 63 | } 64 | }; 65 | 66 | /** 67 | * Common function for copy objects 68 | * @param obj 69 | * @returns {*} 70 | */ 71 | duel.clone = function (obj) { 72 | var copy; 73 | 74 | if (null == obj || "object" != typeof obj) return obj; 75 | 76 | if (obj instanceof Object) { 77 | copy = {}; 78 | for (var attr in obj) { 79 | if (obj.hasOwnProperty(attr)) copy[attr] = duel.clone(obj[attr]); 80 | } 81 | return copy; 82 | } 83 | 84 | throw new Error("Unable to copy obj! Its type isn't supported."); 85 | }; 86 | 87 | /** 88 | * @abstract 89 | */ 90 | duel.DuelAbstractChannel = (function () { 91 | /** 92 | * @constructor 93 | */ 94 | var DuelAbstractChannel = function () {}; 95 | 96 | /** 97 | * @returns {string} 98 | */ 99 | DuelAbstractChannel.prototype.getName = function () { 100 | return this._name; 101 | }; 102 | 103 | /** 104 | * @returns {boolean} 105 | */ 106 | DuelAbstractChannel.prototype.setCurrentWindowAsMaster = function () { 107 | return true; 108 | }; 109 | 110 | /** 111 | * @returns {boolean} 112 | */ 113 | DuelAbstractChannel.prototype.currentWindowIsMaster = function () { 114 | return true; 115 | }; 116 | 117 | /** 118 | * Only master can sends broadcast 119 | * @param {string} trigger 120 | */ 121 | DuelAbstractChannel.prototype.broadcast = function (trigger) { 122 | if (this.currentWindowIsMaster()) { 123 | this.executeTrigger({ 124 | name: trigger, 125 | args: Array.prototype.slice.call(arguments, 1) 126 | }); 127 | } 128 | }; 129 | 130 | /** 131 | * Alias of broadcast 132 | * @param {string} trigger 133 | */ 134 | DuelAbstractChannel.prototype.emit = function () { 135 | this.broadcast.apply(this, arguments); 136 | }; 137 | 138 | /** 139 | * Get storage variable content 140 | * @param {string} varName 141 | * @return {object|string|number|boolean} value 142 | */ 143 | DuelAbstractChannel.prototype.getItem = function (varName) { 144 | varName += ':dueljs:variable:' + this.getName(); 145 | return this.__storage ? ((typeof angular == "object") ? angular.fromJson(this.__storage.getItem(varName)) : JSON.parse(this.__storage.getItem(varName), true)) : null; 146 | }; 147 | 148 | /** 149 | * Set storage variable content 150 | * @param {string} varName 151 | * @param {object|string|number|boolean} value 152 | */ 153 | DuelAbstractChannel.prototype.setItem = function (varName, value) { 154 | if (this.__storage) { 155 | varName += ':dueljs:variable:' + this.getName(); 156 | this.__storage.setItem(varName, (typeof angular == "object") ? angular.toJson(value) : JSON.stringify(value)); 157 | } 158 | }; 159 | 160 | /** 161 | * Remove storage variable 162 | * @param {string} varName 163 | */ 164 | DuelAbstractChannel.prototype.removeItem = function (varName) { 165 | if (this.__storage) { 166 | varName += ':dueljs:variable:' + this.getName(); 167 | this.__storage.removeItem(varName); 168 | } 169 | }; 170 | 171 | /** 172 | * 173 | * @param {{name: string, args: []}} triggerDetails 174 | * @param {boolean} force 175 | */ 176 | DuelAbstractChannel.prototype.executeTrigger = function (triggerDetails, force) { 177 | force = force || false; 178 | if (!(triggerDetails instanceof Object)) { 179 | throw "triggerDetails should be an Object"; 180 | } 181 | if (!this.currentWindowIsMaster() || force) { 182 | try { 183 | if (this._triggers[triggerDetails.name] instanceof Array) { 184 | this._triggers[triggerDetails.name][0].apply(this, triggerDetails.args); 185 | delete this._triggers[triggerDetails.name]; 186 | } else { 187 | this._triggers[triggerDetails.name].apply(this, triggerDetails.args); 188 | } 189 | } catch (e) { 190 | if (!duel.noWarnings) { 191 | if (typeof angular == 'object') { 192 | console.warn('DuelJS caught exception, maybe you didn\'t know that the functions inside the dueljs events use a their own scopes', e); 193 | } else { 194 | console.warn('DuelJS caught exception', e); 195 | } 196 | } 197 | } 198 | } 199 | }; 200 | 201 | /** 202 | * @param {string} trigger 203 | * @param {function|[]} callback 204 | */ 205 | DuelAbstractChannel.prototype.on = function (trigger, callback) { 206 | if (!this._triggers) { 207 | /** 208 | * @type {{}} 209 | * @private 210 | */ 211 | this._triggers = {}; 212 | } 213 | 214 | this._triggers[trigger] = callback; 215 | }; 216 | 217 | /** 218 | * @param {string} trigger 219 | * @param {function} callback 220 | */ 221 | DuelAbstractChannel.prototype.once = function (trigger, callback) { 222 | this.on(trigger, [callback]); 223 | }; 224 | 225 | /** 226 | * @param {string} trigger 227 | */ 228 | DuelAbstractChannel.prototype.off = function (trigger) { 229 | try { 230 | delete this._triggers[trigger]; 231 | } catch (e) {} 232 | }; 233 | 234 | return DuelAbstractChannel 235 | })(); 236 | 237 | /** 238 | * @returns {number} 239 | * @private 240 | */ 241 | duel._generateWindowID = function () { 242 | this._windowID = +Math.random().toString().split('.')[1]; 243 | return this._windowID; 244 | }; 245 | 246 | /** 247 | * Get unique tab ID 248 | * @returns {number} 249 | */ 250 | duel.getWindowID = function () { 251 | return this._windowID ? this._windowID : this._generateWindowID(); 252 | }; 253 | 254 | /** 255 | * @param {string} name 256 | * @constructor 257 | */ 258 | duel.DuelLocalStorageChannel = function (name) { 259 | /** 260 | * Name of current channel 261 | * @type {string} 262 | * @private 263 | */ 264 | this._name = name; 265 | this.__storage = window.localStorage; 266 | this.setCurrentWindowAsMaster(); 267 | }; 268 | 269 | /** 270 | * @type {Object|Function|DuelAbstractChannel} 271 | */ 272 | duel.DuelLocalStorageChannel.prototype = duel.clone(duel.DuelAbstractChannel.prototype); 273 | 274 | /** 275 | * Makes current tab as master tab 276 | * @returns {boolean} 277 | */ 278 | duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster = function () { 279 | var i, len, ch, wIndex, wID = duel.getWindowID(), 280 | chName = 'dueljs_channel_' + this.getName(); 281 | if (ch = this.__storage.getItem(chName)) { 282 | for (ch = JSON.parse(ch), wIndex = -1, i = 0, len = ch.length; i < len; i++) { 283 | ch[i].master = false; 284 | if (ch[i].id === wID) { 285 | wIndex = i 286 | } 287 | } 288 | if (wIndex === -1) { 289 | ch.push({ 290 | id: wID, 291 | master: true 292 | }) 293 | } else { 294 | ch[wIndex].master = true 295 | } 296 | this.__storage.setItem(chName, JSON.stringify(ch)); 297 | } else { 298 | this.__storage.setItem(chName, JSON.stringify([{ 299 | id: wID, 300 | master: true 301 | }])); 302 | } 303 | return true; 304 | }; 305 | 306 | /** 307 | * Check current tab in this channel is master or not 308 | * @returns {boolean} 309 | */ 310 | duel.DuelLocalStorageChannel.prototype.currentWindowIsMaster = function () { 311 | var i, len, ch, wIndex, wID = duel.getWindowID(), 312 | chName = 'dueljs_channel_' + this.getName(); 313 | if (ch = this.__storage.getItem(chName)) { 314 | for (ch = JSON.parse(ch), wIndex = -1, i = 0, len = ch.length; i < len; i++) { 315 | if (ch[i].id === wID) { 316 | wIndex = i; 317 | break; 318 | } 319 | } 320 | return (wIndex === -1) ? false : ch[wIndex].master; 321 | } else { 322 | return false; 323 | } 324 | }; 325 | 326 | /** 327 | * Only master can sends broadcast 328 | */ 329 | duel.DuelLocalStorageChannel.prototype.broadcast = function (trigger) { 330 | if (this.currentWindowIsMaster()) { 331 | // broadcast new task 332 | this.__storage.setItem('dueljs_trigger', JSON.stringify({ 333 | channelName: this.getName(), 334 | triggerDetails: { 335 | name: trigger, 336 | args: Array.prototype.slice.call(arguments, 1) 337 | } 338 | })); 339 | // broadcast that something happened 340 | this.__storage.setItem('dueljs_trigger_event_key', +Math.random().toString().split('.')[1]); 341 | } 342 | }; 343 | 344 | /** 345 | * @param {string} name 346 | * @constructor 347 | */ 348 | duel.DuelFakeChannel = function (name) { 349 | /** 350 | * Name of current channel 351 | * @type {string} 352 | * @private 353 | */ 354 | this._name = name; 355 | if (!duel.noWarnings) { 356 | console.error('DuelJS warning: instanceof DuelFakeChannel was created, ' + 357 | 'check the localStorage support in your browser'); 358 | } 359 | }; 360 | 361 | /** 362 | * @type {Object|Function|DuelAbstractChannel} 363 | */ 364 | duel.DuelFakeChannel.prototype = duel.clone(duel.DuelAbstractChannel.prototype); 365 | 366 | /** 367 | * @type {Array} 368 | */ 369 | duel.activeChannels = []; 370 | 371 | /** 372 | * Create new channel 373 | * @param name 374 | * @returns {duel.DuelLocalStorageChannel} 375 | */ 376 | duel.channel = function (name) { 377 | for (var chID in duel.activeChannels) { 378 | if (duel.activeChannels.hasOwnProperty(chID) && 379 | duel.activeChannels[chID].getName() == name) { 380 | return duel.activeChannels[chID]; 381 | } 382 | } 383 | var channel = this.isLocalStorageAvailable() ? new this.DuelLocalStorageChannel(name) : new this.DuelFakeChannel(name); 384 | duel.activeChannels.push(channel); 385 | return channel; 386 | }; 387 | 388 | /** 389 | * Take the all channels in current window and 390 | * set current window as master for all of them 391 | */ 392 | duel.makeCurrentWindowMaster = function () { 393 | for (var i = duel.activeChannels.length - 1; i >= 0; i--) { 394 | try { 395 | duel.activeChannels[i].setCurrentWindowAsMaster(); 396 | } catch (e) { 397 | // stop to exceptions 398 | } 399 | } 400 | }; 401 | 402 | /** 403 | * Makes tab focus trigger for special browsers 404 | */ 405 | window.onfocus = function () { 406 | duel.makeCurrentWindowMaster(); 407 | }; 408 | 409 | /** 410 | * Take first channel in current window and check is it master or not 411 | * @returns {boolean} 412 | */ 413 | window.isMaster = function () { 414 | return duel.activeChannels.length ? 415 | duel.activeChannels[0].currentWindowIsMaster() : true; 416 | }; 417 | 418 | /** 419 | * Cross-browser addEvent method 420 | */ 421 | duel.addEvent = (function () { 422 | if (document.addEventListener) { 423 | return function (el, type, fn) { 424 | if (el && el.nodeName || el === window) { 425 | el.addEventListener(type, fn, false); 426 | } else if (el && el.length) { 427 | for (var i = 0; i < el.length; i++) { 428 | addEvent(el[i], type, fn); 429 | } 430 | } 431 | }; 432 | } else { 433 | return function (el, type, fn) { 434 | if (el && el.nodeName || el === window) { 435 | el.attachEvent('on' + type, function () { 436 | return fn.call(el, window.event); 437 | }); 438 | } else if (el && el.length) { 439 | for (var i = 0; i < el.length; i++) { 440 | addEvent(el[i], type, fn); 441 | } 442 | } 443 | }; 444 | } 445 | })(); 446 | 447 | /** 448 | * Makes tab focus trigger for modern browsers (more correctly) 449 | */ 450 | duel.addEvent(window, 'visibilitychange', function () { 451 | if (!document.hidden) { 452 | duel.makeCurrentWindowMaster(); 453 | } 454 | }); 455 | 456 | /** 457 | * @param event 458 | */ 459 | duel.storageEvent = function (event) { 460 | if (event.key == 'dueljs_trigger') { 461 | /** 462 | * @type {{channelName: string, triggerDetails: {name: string, args: []}}} 463 | */ 464 | var eventDetails = JSON.parse(event.newValue); 465 | for (var i = duel.activeChannels.length - 1; i >= 0; i--) { 466 | try { 467 | if (duel.activeChannels[i].getName() == eventDetails.channelName) { 468 | duel.activeChannels[i].executeTrigger(eventDetails.triggerDetails); 469 | } 470 | } catch (e) { 471 | // stop to exceptions 472 | } 473 | } 474 | } 475 | }; 476 | 477 | /** 478 | * Broadcast events engine 479 | */ 480 | if (duel.isLocalStorageAvailable()) { 481 | duel.storageOldTriggerValue = window.localStorage.getItem('dueljs_trigger_event_key'); 482 | if (duel.useStorageEvent) { 483 | /** 484 | * Callback doesn't work in the master window 485 | * See html5 storage event documentation 486 | */ 487 | duel.addEvent(window, 'storage', function (e) { 488 | var event = e || event || window.event; 489 | if (event.key == 'dueljs_trigger_event_key' && event.newValue != duel.storageOldTriggerValue) { 490 | duel.storageOldTriggerValue = window.localStorage.getItem('dueljs_trigger_event_key'); 491 | duel.storageEvent({ 492 | key: 'dueljs_trigger', 493 | newValue: window.localStorage.getItem('dueljs_trigger') 494 | }); 495 | } 496 | }); 497 | } else { 498 | setInterval(function () { 499 | if (window.localStorage.getItem('dueljs_trigger_event_key') != duel.storageOldTriggerValue) { 500 | duel.storageOldTriggerValue = window.localStorage.getItem('dueljs_trigger_event_key'); 501 | duel.storageEvent({ 502 | key: 'dueljs_trigger', 503 | newValue: window.localStorage.getItem('dueljs_trigger') 504 | }); 505 | } 506 | }, 100); 507 | } 508 | duel.addEvent(window, 'pagehide', function () { 509 | var wID = duel.getWindowID(), 510 | ch, len, i, j, chName, wIndex; 511 | for (i = duel.activeChannels.length - 1; i >= 0; i--) { 512 | try { 513 | chName = 'dueljs_channel_' + duel.activeChannels[i].getName(); 514 | if (ch = window.localStorage.getItem(chName)) { 515 | for (ch = JSON.parse(ch), wIndex = -1, j = 0, len = ch.length; j < len; j++) { 516 | if (ch[j].id === wID) { 517 | if (ch[j].master) { 518 | ch[j].master = false; 519 | ch[0].master = true; 520 | } 521 | ch.splice(j, 1); 522 | window.localStorage.setItem(chName, JSON.stringify(ch)); 523 | break; 524 | } 525 | } 526 | } 527 | } catch (e) { 528 | // stop to exceptions 529 | } 530 | } 531 | }); 532 | } 533 | 534 | return duel; 535 | })); 536 | -------------------------------------------------------------------------------- /public/lib/duel.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"use strict";"function"==typeof define&&define.amd?define(function(){return t(e)}):"object"==typeof exports?module.exports=t(e):e.duel=t(e)}("undefined"!=typeof window?window:this,function(e){var t={};return t.useStorageEvent=!/trident|MSIE/i.test(navigator.userAgent),t.noWarnings=!0,t.isLocalStorageAvailable=function(){try{var e="undefined"!=typeof localStorage&&"setItem"in localStorage&&localStorage.setItem;if(!e)return!1;var t=new Date;return localStorage.setItem(t,t),localStorage.removeItem(t),!0}catch(n){return!1}},t.clone=function(e){var n;if(null==e||"object"!=typeof e)return e;if(e instanceof Object){n={};for(var r in e)e.hasOwnProperty(r)&&(n[r]=t.clone(e[r]));return n}throw new Error("Unable to copy obj! Its type isn't supported.")},t.DuelAbstractChannel=function(){var e=function(){};return e.prototype.getName=function(){return this._name},e.prototype.setCurrentWindowAsMaster=function(){return!0},e.prototype.currentWindowIsMaster=function(){return!0},e.prototype.broadcast=function(e){this.currentWindowIsMaster()&&this.executeTrigger({name:e,args:Array.prototype.slice.call(arguments,1)})},e.prototype.emit=function(){this.broadcast.apply(this,arguments)},e.prototype.getItem=function(e){return e+=":dueljs:variable:"+this.getName(),this.__storage?"object"==typeof angular?angular.fromJson(this.__storage.getItem(e)):JSON.parse(this.__storage.getItem(e),!0):null},e.prototype.setItem=function(e,t){this.__storage&&(e+=":dueljs:variable:"+this.getName(),this.__storage.setItem(e,"object"==typeof angular?angular.toJson(t):JSON.stringify(t)))},e.prototype.removeItem=function(e){this.__storage&&(e+=":dueljs:variable:"+this.getName(),this.__storage.removeItem(e))},e.prototype.executeTrigger=function(e,n){if(n=n||!1,!(e instanceof Object))throw"triggerDetails should be an Object";if(!this.currentWindowIsMaster()||n)try{this._triggers[e.name]instanceof Array?(this._triggers[e.name][0].apply(this,e.args),delete this._triggers[e.name]):this._triggers[e.name].apply(this,e.args)}catch(r){t.noWarnings||("object"==typeof angular?console.warn("DuelJS caught exception, maybe you didn't know that the functions inside the dueljs events use a their own scopes",r):console.warn("DuelJS caught exception",r))}},e.prototype.on=function(e,t){this._triggers||(this._triggers={}),this._triggers[e]=t},e.prototype.once=function(e,t){this.on(e,[t])},e.prototype.off=function(e){try{delete this._triggers[e]}catch(t){}},e}(),t._generateWindowID=function(){return this._windowID=+Math.random().toString().split(".")[1],this._windowID},t.getWindowID=function(){return this._windowID?this._windowID:this._generateWindowID()},t.DuelLocalStorageChannel=function(t){this._name=t,this.__storage=e.localStorage,this.setCurrentWindowAsMaster()},t.DuelLocalStorageChannel.prototype=t.clone(t.DuelAbstractChannel.prototype),t.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster=function(){var e,n,r,a,o=t.getWindowID(),i="dueljs_channel_"+this.getName();if(r=this.__storage.getItem(i)){for(r=JSON.parse(r),a=-1,e=0,n=r.length;n>e;e++)r[e].master=!1,r[e].id===o&&(a=e);-1===a?r.push({id:o,master:!0}):r[a].master=!0,this.__storage.setItem(i,JSON.stringify(r))}else this.__storage.setItem(i,JSON.stringify([{id:o,master:!0}]));return!0},t.DuelLocalStorageChannel.prototype.currentWindowIsMaster=function(){var e,n,r,a,o=t.getWindowID(),i="dueljs_channel_"+this.getName();if(r=this.__storage.getItem(i)){for(r=JSON.parse(r),a=-1,e=0,n=r.length;n>e;e++)if(r[e].id===o){a=e;break}return-1===a?!1:r[a].master}return!1},t.DuelLocalStorageChannel.prototype.broadcast=function(e){this.currentWindowIsMaster()&&(this.__storage.setItem("dueljs_trigger",JSON.stringify({channelName:this.getName(),triggerDetails:{name:e,args:Array.prototype.slice.call(arguments,1)}})),this.__storage.setItem("dueljs_trigger_event_key",+Math.random().toString().split(".")[1]))},t.DuelFakeChannel=function(e){this._name=e,t.noWarnings||console.error("DuelJS warning: instanceof DuelFakeChannel was created, check the localStorage support in your browser")},t.DuelFakeChannel.prototype=t.clone(t.DuelAbstractChannel.prototype),t.activeChannels=[],t.channel=function(e){for(var n in t.activeChannels)if(t.activeChannels.hasOwnProperty(n)&&t.activeChannels[n].getName()==e)return t.activeChannels[n];var r=this.isLocalStorageAvailable()?new this.DuelLocalStorageChannel(e):new this.DuelFakeChannel(e);return t.activeChannels.push(r),r},t.makeCurrentWindowMaster=function(){for(var e=t.activeChannels.length-1;e>=0;e--)try{t.activeChannels[e].setCurrentWindowAsMaster()}catch(n){}},e.onfocus=function(){t.makeCurrentWindowMaster()},e.isMaster=function(){return t.activeChannels.length?t.activeChannels[0].currentWindowIsMaster():!0},t.addEvent=function(){return document.addEventListener?function(t,n,r){if(t&&t.nodeName||t===e)t.addEventListener(n,r,!1);else if(t&&t.length)for(var a=0;a=0;r--)try{t.activeChannels[r].getName()==n.channelName&&t.activeChannels[r].executeTrigger(n.triggerDetails)}catch(a){}},t.isLocalStorageAvailable()&&(t.storageOldTriggerValue=e.localStorage.getItem("dueljs_trigger_event_key"),t.useStorageEvent?t.addEvent(e,"storage",function(n){var r=n||r||e.event;"dueljs_trigger_event_key"==r.key&&r.newValue!=t.storageOldTriggerValue&&(t.storageOldTriggerValue=e.localStorage.getItem("dueljs_trigger_event_key"),t.storageEvent({key:"dueljs_trigger",newValue:e.localStorage.getItem("dueljs_trigger")}))}):setInterval(function(){e.localStorage.getItem("dueljs_trigger_event_key")!=t.storageOldTriggerValue&&(t.storageOldTriggerValue=e.localStorage.getItem("dueljs_trigger_event_key"),t.storageEvent({key:"dueljs_trigger",newValue:e.localStorage.getItem("dueljs_trigger")}))},100),t.addEvent(e,"unload",function(){var n,r,a,o,i,s,l=t.getWindowID();for(a=t.activeChannels.length-1;a>=0;a--)try{if(i="dueljs_channel_"+t.activeChannels[a].getName(),n=e.localStorage.getItem(i))for(n=JSON.parse(n),s=-1,o=0,r=n.length;r>o;o++)if(n[o].id===l){n[o].master&&(n[o].master=!1,n[0].master=!0),n.splice(o,1),e.localStorage.setItem(i,JSON.stringify(n));break}}catch(g){}})),t}); 2 | //# sourceMappingURL=duel.min.js.map -------------------------------------------------------------------------------- /public/lib/duel.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"public/lib/duel.min.js","sources":["public/lib/duel.js"],"names":["root","factory","define","amd","exports","module","duel","window","this","useStorageEvent","test","navigator","userAgent","noWarnings","isLocalStorageAvailable","hasStorage","localStorage","setItem","uid","Date","removeItem","e","clone","obj","copy","Object","attr","hasOwnProperty","Error","DuelAbstractChannel","prototype","getName","_name","setCurrentWindowAsMaster","currentWindowIsMaster","broadcast","trigger","executeTrigger","name","args","Array","slice","call","arguments","emit","apply","getItem","varName","__storage","angular","fromJson","JSON","parse","value","toJson","stringify","triggerDetails","force","_triggers","console","warn","on","callback","once","off","_generateWindowID","_windowID","Math","random","toString","split","getWindowID","DuelLocalStorageChannel","i","len","ch","wIndex","wID","chName","length","master","id","push","channelName","DuelFakeChannel","error","activeChannels","channel","chID","makeCurrentWindowMaster","onfocus","isMaster","addEvent","document","addEventListener","el","type","fn","nodeName","attachEvent","event","hidden","storageEvent","key","eventDetails","newValue","storageOldTriggerValue","setInterval","j","splice"],"mappings":"CASC,SAAUA,EAAMC,GACf,YAEsB,mBAAXC,SAAyBA,OAAOC,IAEzCD,OAAO,WAEL,MAAOD,GAAQD,KAEW,gBAAZI,SAGhBC,OAAOD,QAAUH,EAAQD,GAGzBA,EAAKM,KAAOL,EAAQD,IAEJ,mBAAXO,QAAyBA,OAASC,KAAM,SAAUD,GACzD,GAAID,KA0fJ,OAjfAA,GAAKG,iBAAmB,gBAAgBC,KAAKC,UAAUC,WAOvDN,EAAKO,YAAa,EAMlBP,EAAKQ,wBAA0B,WAC7B,IACE,GAAIC,GAAqC,mBAAjBC,eAAiC,WAAaA,eAAiBA,aAAaC,OACpG,KAAKF,EACH,OAAO,CAGT,IAAIG,GAAM,GAAIC,KAGd,OAFAH,cAAaC,QAAQC,EAAKA,GAC1BF,aAAaI,WAAWF,IACjB,EACP,MAAOG,GACP,OAAO,IASXf,EAAKgB,MAAQ,SAAUC,GACrB,GAAIC,EAEJ,IAAI,MAAQD,GAAO,gBAAmBA,GAAK,MAAOA,EAElD,IAAIA,YAAeE,QAAQ,CACzBD,IACA,KAAK,GAAIE,KAAQH,GACXA,EAAII,eAAeD,KAAOF,EAAKE,GAAQpB,EAAKgB,MAAMC,EAAIG,IAE5D,OAAOF,GAGT,KAAM,IAAII,OAAM,kDAMlBtB,EAAKuB,oBAAsB,WAIzB,GAAIA,GAAsB,YA4I1B,OAvIAA,GAAoBC,UAAUC,QAAU,WACtC,MAAOvB,MAAKwB,OAMdH,EAAoBC,UAAUG,yBAA2B,WACvD,OAAO,GAMTJ,EAAoBC,UAAUI,sBAAwB,WACpD,OAAO,GAOTL,EAAoBC,UAAUK,UAAY,SAAUC,GAC9C5B,KAAK0B,yBACP1B,KAAK6B,gBACHC,KAAMF,EACNG,KAAMC,MAAMV,UAAUW,MAAMC,KAAKC,UAAW,MASlDd,EAAoBC,UAAUc,KAAO,WACnCpC,KAAK2B,UAAUU,MAAMrC,KAAMmC,YAQ7Bd,EAAoBC,UAAUgB,QAAU,SAAUC,GAEhD,MADAA,IAAW,oBAAsBvC,KAAKuB,UAC/BvB,KAAKwC,UAAgC,gBAAXC,SAAuBA,QAAQC,SAAS1C,KAAKwC,UAAUF,QAAQC,IAAYI,KAAKC,MAAM5C,KAAKwC,UAAUF,QAAQC,IAAU,GAAS,MAQnKlB,EAAoBC,UAAUb,QAAU,SAAU8B,EAASM,GACrD7C,KAAKwC,YACPD,GAAW,oBAAsBvC,KAAKuB,UACtCvB,KAAKwC,UAAU/B,QAAQ8B,EAA4B,gBAAXE,SAAuBA,QAAQK,OAAOD,GAASF,KAAKI,UAAUF,MAQ1GxB,EAAoBC,UAAUV,WAAa,SAAU2B,GAC/CvC,KAAKwC,YACPD,GAAW,oBAAsBvC,KAAKuB,UACtCvB,KAAKwC,UAAU5B,WAAW2B,KAS9BlB,EAAoBC,UAAUO,eAAiB,SAAUmB,EAAgBC,GAEvE,GADAA,EAAQA,IAAS,IACXD,YAA0B/B,SAC9B,KAAM,oCAER,KAAKjB,KAAK0B,yBAA2BuB,EACnC,IACMjD,KAAKkD,UAAUF,EAAelB,eAAiBE,QACjDhC,KAAKkD,UAAUF,EAAelB,MAAM,GAAGO,MAAMrC,KAAMgD,EAAejB,YAC3D/B,MAAKkD,UAAUF,EAAelB,OAErC9B,KAAKkD,UAAUF,EAAelB,MAAMO,MAAMrC,KAAMgD,EAAejB,MAEjE,MAAOlB,GACFf,EAAKO,aACc,gBAAXoC,SACTU,QAAQC,KAAK,oHAAsHvC,GAEnIsC,QAAQC,KAAK,0BAA2BvC,MAWlDQ,EAAoBC,UAAU+B,GAAK,SAAUzB,EAAS0B,GAC/CtD,KAAKkD,YAKRlD,KAAKkD,cAGPlD,KAAKkD,UAAUtB,GAAW0B,GAO5BjC,EAAoBC,UAAUiC,KAAO,SAAU3B,EAAS0B,GACtDtD,KAAKqD,GAAGzB,GAAU0B,KAMpBjC,EAAoBC,UAAUkC,IAAM,SAAU5B,GAC5C,UACS5B,MAAKkD,UAAUtB,GACtB,MAAOf,MAGJQ,KAOTvB,EAAK2D,kBAAoB,WAEvB,MADAzD,MAAK0D,WAAaC,KAAKC,SAASC,WAAWC,MAAM,KAAK,GAC/C9D,KAAK0D,WAOd5D,EAAKiE,YAAc,WACjB,MAAO/D,MAAK0D,UAAY1D,KAAK0D,UAAY1D,KAAKyD,qBAOhD3D,EAAKkE,wBAA0B,SAAUlC,GAMvC9B,KAAKwB,MAAQM,EACb9B,KAAKwC,UAAYzC,EAAOS,aACxBR,KAAKyB,4BAMP3B,EAAKkE,wBAAwB1C,UAAYxB,EAAKgB,MAAMhB,EAAKuB,oBAAoBC,WAM7ExB,EAAKkE,wBAAwB1C,UAAUG,yBAA2B,WAChE,GAAIwC,GAAGC,EAAKC,EAAIC,EAAQC,EAAMvE,EAAKiE,cACjCO,EAAS,kBAAoBtE,KAAKuB,SACpC,IAAI4C,EAAKnE,KAAKwC,UAAUF,QAAQgC,GAAS,CACvC,IAAKH,EAAKxB,KAAKC,MAAMuB,GAAKC,EAAS,GAAIH,EAAI,EAAGC,EAAMC,EAAGI,OAAYL,EAAJD,EAASA,IACtEE,EAAGF,GAAGO,QAAS,EACXL,EAAGF,GAAGQ,KAAOJ,IACfD,EAASH,EAGE,MAAXG,EACFD,EAAGO,MACDD,GAAIJ,EACJG,QAAQ,IAGVL,EAAGC,GAAQI,QAAS,EAEtBxE,KAAKwC,UAAU/B,QAAQ6D,EAAQ3B,KAAKI,UAAUoB,QAE9CnE,MAAKwC,UAAU/B,QAAQ6D,EAAQ3B,KAAKI,YAClC0B,GAAIJ,EACJG,QAAQ,KAGZ,QAAO,GAOT1E,EAAKkE,wBAAwB1C,UAAUI,sBAAwB,WAC7D,GAAIuC,GAAGC,EAAKC,EAAIC,EAAQC,EAAMvE,EAAKiE,cACjCO,EAAS,kBAAoBtE,KAAKuB,SACpC,IAAI4C,EAAKnE,KAAKwC,UAAUF,QAAQgC,GAAS,CACvC,IAAKH,EAAKxB,KAAKC,MAAMuB,GAAKC,EAAS,GAAIH,EAAI,EAAGC,EAAMC,EAAGI,OAAYL,EAAJD,EAASA,IACtE,GAAIE,EAAGF,GAAGQ,KAAOJ,EAAK,CACpBD,EAASH,CACT,OAGJ,MAAmB,KAAXG,GAAiB,EAAQD,EAAGC,GAAQI,OAE5C,OAAO,GAOX1E,EAAKkE,wBAAwB1C,UAAUK,UAAY,SAAUC,GACvD5B,KAAK0B,0BAEP1B,KAAKwC,UAAU/B,QAAQ,iBAAkBkC,KAAKI,WAC5C4B,YAAa3E,KAAKuB,UAClByB,gBACElB,KAAMF,EACNG,KAAMC,MAAMV,UAAUW,MAAMC,KAAKC,UAAW,OAIhDnC,KAAKwC,UAAU/B,QAAQ,4BAA6BkD,KAAKC,SAASC,WAAWC,MAAM,KAAK,MAQ5FhE,EAAK8E,gBAAkB,SAAU9C,GAM/B9B,KAAKwB,MAAQM,EACRhC,EAAKO,YACR8C,QAAQ0B,MAAM,2GAQlB/E,EAAK8E,gBAAgBtD,UAAYxB,EAAKgB,MAAMhB,EAAKuB,oBAAoBC,WAKrExB,EAAKgF,kBAOLhF,EAAKiF,QAAU,SAAUjD,GACvB,IAAK,GAAIkD,KAAQlF,GAAKgF,eACpB,GAAIhF,EAAKgF,eAAe3D,eAAe6D,IACrClF,EAAKgF,eAAeE,GAAMzD,WAAaO,EACvC,MAAOhC,GAAKgF,eAAeE,EAG/B,IAAID,GAAU/E,KAAKM,0BAA4B,GAAIN,MAAKgE,wBAAwBlC,GAAQ,GAAI9B,MAAK4E,gBAAgB9C,EAEjH,OADAhC,GAAKgF,eAAeJ,KAAKK,GAClBA,GAOTjF,EAAKmF,wBAA0B,WAC7B,IAAK,GAAIhB,GAAInE,EAAKgF,eAAeP,OAAS,EAAGN,GAAK,EAAGA,IACnD,IACEnE,EAAKgF,eAAeb,GAAGxC,2BACvB,MAAOZ,MASbd,EAAOmF,QAAU,WACfpF,EAAKmF,2BAOPlF,EAAOoF,SAAW,WAChB,MAAOrF,GAAKgF,eAAeP,OACzBzE,EAAKgF,eAAe,GAAGpD,yBAA0B,GAMrD5B,EAAKsF,SAAW,WACd,MAAIC,UAASC,iBACJ,SAAUC,EAAIC,EAAMC,GACzB,GAAIF,GAAMA,EAAGG,UAAYH,IAAOxF,EAC9BwF,EAAGD,iBAAiBE,EAAMC,GAAI,OACzB,IAAIF,GAAMA,EAAGhB,OAClB,IAAK,GAAIN,GAAI,EAAGA,EAAIsB,EAAGhB,OAAQN,IAC7BmB,SAASG,EAAGtB,GAAIuB,EAAMC,IAKrB,SAAUF,EAAIC,EAAMC,GACzB,GAAIF,GAAMA,EAAGG,UAAYH,IAAOxF,EAC9BwF,EAAGI,YAAY,KAAOH,EAAM,WAC1B,MAAOC,GAAGvD,KAAKqD,EAAIxF,EAAO6F,aAEvB,IAAIL,GAAMA,EAAGhB,OAClB,IAAK,GAAIN,GAAI,EAAGA,EAAIsB,EAAGhB,OAAQN,IAC7BmB,SAASG,EAAGtB,GAAIuB,EAAMC,OAUhC3F,EAAKsF,SAASrF,EAAQ,mBAAoB,WACnCsF,SAASQ,QACZ/F,EAAKmF,4BAOTnF,EAAKgG,aAAe,SAAUF,GAC5B,GAAiB,kBAAbA,EAAMG,IAKR,IAAK,GADDC,GAAerD,KAAKC,MAAMgD,EAAMK,UAC3BhC,EAAInE,EAAKgF,eAAeP,OAAS,EAAGN,GAAK,EAAGA,IACnD,IACMnE,EAAKgF,eAAeb,GAAG1C,WAAayE,EAAarB,aACnD7E,EAAKgF,eAAeb,GAAGpC,eAAemE,EAAahD,gBAErD,MAAOnC,MAUXf,EAAKQ,4BACPR,EAAKoG,uBAAyBnG,EAAOS,aAAa8B,QAAQ,4BACtDxC,EAAKG,gBAKPH,EAAKsF,SAASrF,EAAQ,UAAW,SAAUc,GACzC,GAAI+E,GAAQ/E,GAAK+E,GAAS7F,EAAO6F,KAChB,6BAAbA,EAAMG,KAAqCH,EAAMK,UAAYnG,EAAKoG,yBACpEpG,EAAKoG,uBAAyBnG,EAAOS,aAAa8B,QAAQ,4BAC1DxC,EAAKgG,cACHC,IAAK,iBACLE,SAAUlG,EAAOS,aAAa8B,QAAQ,uBAK5C6D,YAAY,WACNpG,EAAOS,aAAa8B,QAAQ,6BAA+BxC,EAAKoG,yBAClEpG,EAAKoG,uBAAyBnG,EAAOS,aAAa8B,QAAQ,4BAC1DxC,EAAKgG,cACHC,IAAK,iBACLE,SAAUlG,EAAOS,aAAa8B,QAAQ,sBAGzC,KAELxC,EAAKsF,SAASrF,EAAQ,SAAU,WAC9B,GACEoE,GAAID,EAAKD,EAAGmC,EAAG9B,EAAQF,EADrBC,EAAMvE,EAAKiE,aAEf,KAAKE,EAAInE,EAAKgF,eAAeP,OAAS,EAAGN,GAAK,EAAGA,IAC/C,IAEE,GADAK,EAAS,kBAAoBxE,EAAKgF,eAAeb,GAAG1C,UAChD4C,EAAKpE,EAAOS,aAAa8B,QAAQgC,GACnC,IAAKH,EAAKxB,KAAKC,MAAMuB,GAAKC,EAAS,GAAIgC,EAAI,EAAGlC,EAAMC,EAAGI,OAAYL,EAAJkC,EAASA,IACtE,GAAIjC,EAAGiC,GAAG3B,KAAOJ,EAAK,CAChBF,EAAGiC,GAAG5B,SACRL,EAAGiC,GAAG5B,QAAS,EACfL,EAAG,GAAGK,QAAS,GAEjBL,EAAGkC,OAAOD,EAAG,GACbrG,EAAOS,aAAaC,QAAQ6D,EAAQ3B,KAAKI,UAAUoB,GACnD,QAIN,MAAOtD,QAORf"} -------------------------------------------------------------------------------- /test/duel_test.js: -------------------------------------------------------------------------------- 1 | var expect = chai.expect; 2 | 3 | Object.size = function (obj) { 4 | var size = 0, key; 5 | for (key in obj) { 6 | if (obj.hasOwnProperty(key)) size++; 7 | } 8 | return size; 9 | }; 10 | 11 | describe('DuelJS test case 1', function () { 12 | describe('#initialization testing', function () { 13 | 14 | it('duel should be initialized', function () { 15 | expect(duel).to.be.an.instanceof(Object); 16 | }); 17 | 18 | if (!/trident|MSIE/i.test(navigator.userAgent)) { 19 | it('duel should be with useStorageEvent == true with ' + navigator.userAgent, function () { 20 | expect(duel.useStorageEvent).to.equal(true); 21 | }); 22 | } else { 23 | it('duel should be with useStorageEvent == false with ' + navigator.userAgent, function () { 24 | expect(duel.useStorageEvent).to.equal(false); 25 | }); 26 | } 27 | 28 | it('duel.isLocalStorageAvailable should be a function and should returns true', function () { 29 | expect(duel.isLocalStorageAvailable).not.to.be.an('undefined'); 30 | expect(duel.isLocalStorageAvailable()).to.equal(true); 31 | }); 32 | 33 | it('duel integrity should be complete', function () { 34 | expect(duel.clone).not.to.be.an('undefined'); 35 | expect(duel.DuelAbstractChannel).not.to.be.an('undefined'); 36 | expect(duel.getWindowID).not.to.be.an('undefined'); 37 | expect(duel.DuelLocalStorageChannel).not.to.be.an('undefined'); 38 | expect(duel.DuelLocalStorageChannel.prototype.getName).not.to.be.an('undefined'); 39 | expect(duel.DuelLocalStorageChannel.prototype.executeTrigger).not.to.be.an('undefined'); 40 | expect(duel.DuelLocalStorageChannel.prototype.on).not.to.be.an('undefined'); 41 | expect(duel.DuelLocalStorageChannel.prototype.once).not.to.be.an('undefined'); 42 | expect(duel.DuelLocalStorageChannel.prototype.off).not.to.be.an('undefined'); 43 | expect(duel.DuelLocalStorageChannel.prototype.emit).not.to.be.an('undefined'); 44 | expect(duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); 45 | expect(duel.DuelLocalStorageChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); 46 | expect(duel.DuelLocalStorageChannel.prototype.broadcast).not.to.be.an('undefined'); 47 | expect(duel.DuelFakeChannel).not.to.be.an('undefined'); 48 | expect(duel.DuelFakeChannel.prototype.getName).not.to.be.an('undefined'); 49 | expect(duel.DuelFakeChannel.prototype.executeTrigger).not.to.be.an('undefined'); 50 | expect(duel.DuelFakeChannel.prototype.on).not.to.be.an('undefined'); 51 | expect(duel.DuelFakeChannel.prototype.once).not.to.be.an('undefined'); 52 | expect(duel.DuelFakeChannel.prototype.off).not.to.be.an('undefined'); 53 | expect(duel.DuelFakeChannel.prototype.emit).not.to.be.an('undefined'); 54 | expect(duel.DuelFakeChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); 55 | expect(duel.DuelFakeChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); 56 | expect(duel.DuelFakeChannel.prototype.broadcast).not.to.be.an('undefined'); 57 | expect(duel.activeChannels).to.be.an('array'); 58 | expect(duel.channel).not.to.be.an('undefined'); 59 | expect(duel.makeCurrentWindowMaster).not.to.be.an('undefined'); 60 | expect(window.isMaster).not.to.be.an('undefined'); 61 | expect(duel.addEvent).not.to.be.an('undefined'); 62 | expect(duel.storageEvent).not.to.be.an('undefined'); 63 | }); 64 | }); 65 | 66 | describe('#window testing', function () { 67 | 68 | duel.activeChannels = []; 69 | 70 | it('duel.getWindowID should returns unique Window ID', function () { 71 | var windowID = duel.getWindowID(); 72 | expect(windowID).to.be.a('number'); 73 | expect(duel.getWindowID()).to.equal(windowID); 74 | }); 75 | 76 | it('window.isMaster() should be equal true without channels', function () { 77 | expect(window.isMaster()).to.equal(true); 78 | 79 | describe('DuelJS test case 2', function () { 80 | describe('#channel testing', function () { 81 | var channel = duel.channel('_test'); 82 | 83 | it('duel.channel should returns new channel instance', function () { 84 | expect(channel).to.be.an.instanceof(Object); 85 | }); 86 | 87 | it('window.isMaster() should be equal true with initialized channel', function () { 88 | expect(window.isMaster()).to.equal(true); 89 | }); 90 | 91 | it('should not to dublicate an channels', function () { 92 | channel = duel.channel('_test'); 93 | channel = duel.channel('_test'); 94 | var channel2 = duel.channel('_test2'); 95 | var channel3 = duel.channel('_test'); 96 | expect(duel.activeChannels.length).to.equal(2); 97 | expect(channel3).to.equal(channel); 98 | expect(channel.currentWindowIsMaster()).to.equal(true); 99 | expect(channel2.currentWindowIsMaster()).to.equal(true); 100 | expect(channel3.currentWindowIsMaster()).to.equal(true); 101 | }); 102 | 103 | it('should create the event', function (done) { 104 | var _check = -5; 105 | channel.on('test_event', function (a, b, c) { 106 | _check += a + b + c; 107 | expect(_check).to.equal(0); 108 | }); 109 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 110 | expect(channel._triggers).not.to.eql({}); 111 | channel._triggers = {}; 112 | done(); 113 | }); 114 | 115 | it('should remove the event', function (done) { 116 | var _check = -5; 117 | channel.on('test_event', function (a, b, c) { 118 | _check += a + b + c; 119 | }); 120 | channel.off('test_event'); 121 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 122 | expect(_check).to.equal(-5); 123 | expect(channel._triggers).to.eql({}); 124 | done(); 125 | }); 126 | 127 | it('should create the event and remove it after once', function (done) { 128 | var __check = -5; 129 | expect(channel._triggers).to.eql({}); 130 | channel.once('test_event', function (a, b, c) { 131 | __check += a + b + c; 132 | }); 133 | expect(channel._triggers).not.to.eql({}); 134 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 135 | expect(__check).to.equal(0); 136 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 137 | expect(__check).to.equal(0); 138 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 139 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 140 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); 141 | expect(__check).to.equal(0); 142 | expect(channel._triggers).to.eql({}); 143 | channel.off('test_event'); 144 | done(); 145 | }); 146 | 147 | it('should not execute an trigger in a master window', function () { 148 | var __check = -5; 149 | channel.once('test_event', function (a, b, c) { 150 | __check += a + b + c; 151 | }); 152 | channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}); 153 | expect(__check).to.equal(-5); 154 | channel.off('test_event'); 155 | }); 156 | 157 | it('should not to dublicate an triggers', function () { 158 | expect(channel._triggers).to.eql({}); 159 | channel.on('test_event', function () { 160 | return true; 161 | }); 162 | expect(channel._triggers).not.to.eql({}); 163 | expect(Object.size(channel._triggers)).to.eql(1); 164 | channel.on('test_event', function () { 165 | return true; 166 | }); 167 | expect(Object.size(channel._triggers)).to.eql(1); 168 | channel.off('test_event'); 169 | expect(Object.size(channel._triggers)).to.eql(0); 170 | }); 171 | 172 | it('should create item and get him back', function () { 173 | channel.setItem('x', 10); 174 | expect(channel.getItem('x')).to.eql(10); 175 | }); 176 | }); 177 | }); 178 | }); 179 | }); 180 | }); -------------------------------------------------------------------------------- /test/mocha.min.test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /test/mocha.test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /test/mocha.test.umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/phantom.script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PhantomJS Test Script 3 | */ 4 | 5 | var webPage = require('webpage'); 6 | 7 | var page1 = webPage.create(); 8 | var page2 = webPage.create(); 9 | var page3 = webPage.create(); 10 | 11 | function shouldBeEqual(arg1, arg2) { 12 | if (arg1 === arg2) { 13 | console.log(arg1, '===', arg2, 'ok'); 14 | } else { 15 | console.log(arg1, '!==', arg2, 'test failed'); 16 | phantom.exit(); 17 | } 18 | } 19 | 20 | page1.open('test/phantom.tab.html', function () { 21 | 22 | var title1 = page1.evaluate(function () { 23 | return document.title; 24 | }); 25 | 26 | shouldBeEqual(/master/i.test(title1), true); 27 | 28 | page2.open('test/phantom.tab.html', function () { 29 | 30 | page3.open('test/phantom.tab.html', function () { 31 | 32 | var wndID1 = page1.evaluate(function () { 33 | return duel.getWindowID(); 34 | }); 35 | 36 | page2.onCallback = function (data) { 37 | shouldBeEqual(data.wndID, wndID1.toString() + ' "ya"'); 38 | }; 39 | 40 | page3.onCallback = function (data) { 41 | shouldBeEqual(data.wndID, wndID1.toString() + ' "ya"'); 42 | }; 43 | 44 | var broadcast = page1.evaluate(function () { 45 | channel.broadcast('demo_trigger', 'ya', duel.getWindowID()); 46 | return true; 47 | }); 48 | 49 | shouldBeEqual(broadcast, true); 50 | 51 | /** 52 | * Why storage event doesn't work in phantom? 53 | * https://github.com/ariya/phantomjs/issues/12879 54 | */ 55 | 56 | setTimeout(phantom.exit, 2000); 57 | }); 58 | }); 59 | }); -------------------------------------------------------------------------------- /test/phantom.tab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Duel page 6 | 7 | 8 | 9 | 10 |
11 |

Window ...

12 |
Open new window  13 |
16 |
17 | Hint: open the developer console before broadcasting. 18 |
19 |
20 | 33 | 34 | -------------------------------------------------------------------------------- /test/requirejs.main.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | var chai = require('../node_modules/chai/chai'); 3 | var duel = require('../public/lib/duel'); 4 | 5 | mocha.ui('bdd'); 6 | mocha.reporter('html'); 7 | mocha.globals(['duel']); 8 | 9 | var expect = chai.expect; 10 | 11 | describe('DuelJS test case requirejs', function() { 12 | describe('#initialization testing', function() { 13 | 14 | it('duel should be initialized', function() { 15 | expect(duel).to.be.an.instanceof(Object); 16 | }); 17 | 18 | it('duel integrity should be complete', function() { 19 | expect(duel.clone).not.to.be.an('undefined'); 20 | expect(duel.DuelAbstractChannel).not.to.be.an('undefined'); 21 | expect(duel.getWindowID).not.to.be.an('undefined'); 22 | expect(duel.DuelLocalStorageChannel).not.to.be.an('undefined'); 23 | expect(duel.DuelLocalStorageChannel.prototype.getName).not.to.be.an('undefined'); 24 | expect(duel.DuelLocalStorageChannel.prototype.executeTrigger).not.to.be.an('undefined'); 25 | expect(duel.DuelLocalStorageChannel.prototype.on).not.to.be.an('undefined'); 26 | expect(duel.DuelLocalStorageChannel.prototype.once).not.to.be.an('undefined'); 27 | expect(duel.DuelLocalStorageChannel.prototype.off).not.to.be.an('undefined'); 28 | expect(duel.DuelLocalStorageChannel.prototype.emit).not.to.be.an('undefined'); 29 | expect(duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); 30 | expect(duel.DuelLocalStorageChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); 31 | expect(duel.DuelLocalStorageChannel.prototype.broadcast).not.to.be.an('undefined'); 32 | expect(duel.DuelFakeChannel).not.to.be.an('undefined'); 33 | expect(duel.DuelFakeChannel.prototype.getName).not.to.be.an('undefined'); 34 | expect(duel.DuelFakeChannel.prototype.executeTrigger).not.to.be.an('undefined'); 35 | expect(duel.DuelFakeChannel.prototype.on).not.to.be.an('undefined'); 36 | expect(duel.DuelFakeChannel.prototype.once).not.to.be.an('undefined'); 37 | expect(duel.DuelFakeChannel.prototype.off).not.to.be.an('undefined'); 38 | expect(duel.DuelFakeChannel.prototype.emit).not.to.be.an('undefined'); 39 | expect(duel.DuelFakeChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); 40 | expect(duel.DuelFakeChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); 41 | expect(duel.DuelFakeChannel.prototype.broadcast).not.to.be.an('undefined'); 42 | expect(duel.activeChannels).to.be.an('array'); 43 | expect(duel.channel).not.to.be.an('undefined'); 44 | expect(duel.makeCurrentWindowMaster).not.to.be.an('undefined'); 45 | expect(window.isMaster).not.to.be.an('undefined'); 46 | expect(duel.addEvent).not.to.be.an('undefined'); 47 | expect(duel.storageEvent).not.to.be.an('undefined'); 48 | }); 49 | }); 50 | }); 51 | 52 | if (window.mochaPhantomJS) { 53 | mochaPhantomJS.run(); 54 | } else { 55 | mocha.run(); 56 | } 57 | }); 58 | --------------------------------------------------------------------------------