├── .gitignore ├── src ├── websocket │ ├── Request.php │ ├── Response.php │ ├── Session.php │ └── Server.php ├── wamp │ ├── assets │ │ ├── Bundle.php │ │ └── js │ │ │ ├── socketResource.js │ │ │ └── autobahn.js │ ├── Server.php │ ├── ContextInterface.php │ ├── Route.php │ └── Protocol.php └── templates │ ├── websocket.php │ └── phpd.conf ├── readme.md ├── bin └── phpd.template.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | .idea 3 | vendor -------------------------------------------------------------------------------- /src/websocket/Request.php: -------------------------------------------------------------------------------- 1 | pubsub = new \PHPDaemon\PubSub\PubSub(); 12 | parent::__construct($name); 13 | } 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/websocket/Response.php: -------------------------------------------------------------------------------- 1 | isSuccess = true; 14 | } 15 | public function fail(\Exception $e) { 16 | $this->data = $e->getTraceAsString(); 17 | $this->isSuccess = false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/templates/websocket.php: -------------------------------------------------------------------------------- 1 | 'app-frontend-websocket', 7 | 'components' => [ 8 | 'request' => array( 9 | 'class' => 'nizsheanez\wamp\Request', 10 | ), 11 | 'response' => array( 12 | 'class' => 'nizsheanez\wamp\Response', 13 | ), 14 | ], 15 | ); 16 | 17 | return \yii\helpers\ArrayHelper::merge($main, $websocket); 18 | -------------------------------------------------------------------------------- /src/websocket/Session.php: -------------------------------------------------------------------------------- 1 | =5.4.0", 22 | "yiisoft/yii2": "dev-master", 23 | "kakserpom/phpdaemon": "dev-master" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "nizsheanez\\websocket\\": "src/websocket", 28 | "nizsheanez\\wamp\\": "src/wamp" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/websocket/Server.php: -------------------------------------------------------------------------------- 1 | initRoutes(); 16 | } 17 | 18 | public function initRoutes() 19 | { 20 | $appInstance = $this; 21 | $path = ''; 22 | \PHPDaemon\Servers\WebSocket\Pool::getInstance()->addRoute($path, function ($client) use ($path, $appInstance) { 23 | return $appInstance->getRoute($path, $client); 24 | }); 25 | } 26 | 27 | public function getRoute($path, $client) 28 | { 29 | switch ($path) { 30 | case '': 31 | $route = new $this->routeClass($client, $this); 32 | $route->id = uniqid(); 33 | $route->yiiAppClass = $this->config->yiiappclass->value; 34 | $route->yiiAppConfig = $this->config->yiiappconfig->value; 35 | $this->sessions[$route->id] = $route; 36 | return $route; 37 | } 38 | 39 | } 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/wamp/Route.php: -------------------------------------------------------------------------------- 1 | client->onSessionStart(function ($event) {}); //temporary fix of non started sessions 29 | $wamp = $this->wamp = new Protocol($this); 30 | $wamp->onOpen(); 31 | } 32 | 33 | 34 | public function onFrame($message, $type) 35 | { 36 | $this->beforeOnMessage(); 37 | $message = substr($message, 0, strrpos($message, ']') + 1); 38 | 39 | $this->wamp->onMessage($message); 40 | $this->afterOnMessage(); 41 | } 42 | 43 | /** 44 | * Create Yii application 45 | */ 46 | public function beforeOnMessage() 47 | { 48 | $config = require Yii::getAlias($this->yiiAppConfig); 49 | $class = $this->yiiAppClass; 50 | new $class($config); 51 | } 52 | 53 | /** 54 | * Garbage Collection 55 | * 56 | * close sessions, destroy application, etc. 57 | */ 58 | public function afterOnMessage() 59 | { 60 | $this->client->sessionCommit(); 61 | Yii::$app->session->close(); 62 | foreach (Yii::$app->getComponents() as $component) { 63 | unset($component); 64 | } 65 | Yii::$app = null; 66 | } 67 | 68 | 69 | public function onFinish() 70 | { 71 | $this->appInstance->pubsub->unsubFromAll($this); 72 | 73 | return $this; 74 | } 75 | 76 | 77 | public function setPrefix($prefix, $value) 78 | { 79 | $this->prefixes[$prefix] = $value; 80 | } 81 | 82 | public function getPrefix($prefix) 83 | { 84 | if (array_key_exists($prefix, $this->prefixes)) { 85 | return $this->prefixes[$prefix]; 86 | } else { 87 | return false; 88 | } 89 | } 90 | 91 | public function getId() 92 | { 93 | return $this->id; 94 | } 95 | 96 | 97 | public function call($id, $topic, array $params) 98 | { 99 | Yii::$app->request->setUrl(str_replace($this->client->server['HTTP_ORIGIN'], '', $topic)); 100 | Daemon::log(Yii::$app->request->getUrl()); 101 | 102 | Yii::$app->request->setQueryParams($params); 103 | try { 104 | Yii::$app->run(); 105 | } catch (\Exception $e) { 106 | $this->wamp->error($id, Yii::$app->response->data); 107 | Daemon::log($e); 108 | return true; 109 | } 110 | $this->wamp->result($id, Yii::$app->response->data); 111 | } 112 | 113 | public function subscribe($id) 114 | { 115 | $this->appInstance->pubsub->sub($id, $this->client, function () { 116 | }); 117 | } 118 | 119 | public function unsubscribe($id) 120 | { 121 | $this->appInstance->pubsub->unsub($id, $this); 122 | } 123 | 124 | public function publish($uri, $event, $exclude, $eligible) 125 | { 126 | $this->appInstance->pubsub->pub($uri, $event); 127 | } 128 | 129 | public function onClose() 130 | { 131 | $this->appInstance->pubsub->unsubFromAll($this); 132 | } 133 | 134 | public function onError(\Exception $e) 135 | { 136 | return $this->_decorating->onError($this->client, $e); 137 | } 138 | 139 | public function send($message) 140 | { 141 | $this->client->sendFrame($message, 'STRING'); 142 | 143 | return $this; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/wamp/Protocol.php: -------------------------------------------------------------------------------- 1 | context = $context; 30 | } 31 | 32 | public function onOpen() 33 | { 34 | $message = [ 35 | static::MSG_WELCOME, 36 | $this->context->getId(), 37 | 1, 38 | 'yii2-websocket-application' 39 | ]; 40 | $this->send($message); 41 | } 42 | 43 | /** 44 | * Respond with an error to a client call 45 | * 46 | * @param string $id The unique ID given by the client to respond to 47 | * @param string $errorUri The URI given to identify the specific error 48 | * @param string $desc A developer-oriented description of the error 49 | * @param string $details An optional human readable detail message to send back 50 | */ 51 | public function callError($id, $errorUri, $desc = '', $details = null) 52 | { 53 | $data = [ 54 | static::MSG_CALL_ERROR, 55 | $id, 56 | $errorUri, 57 | $desc 58 | ]; 59 | 60 | if (null !== $details) { 61 | $data[] = $details; 62 | } 63 | 64 | return $this->send($data); 65 | } 66 | 67 | /** 68 | * @param string $topic The topic to broadcast to 69 | * @param mixed $msg Data to send with the event. Anything that is json'able 70 | */ 71 | public function event($topic, $msg) 72 | { 73 | $message = [ 74 | static::MSG_EVENT, 75 | (string)$topic, 76 | $msg 77 | ]; 78 | return $this->send($message); 79 | } 80 | 81 | /** 82 | * @param string $curie 83 | * @param string $uri 84 | */ 85 | public function prefix($curie, $uri) 86 | { 87 | $this->context->setPrefix($curie, (string)$uri); 88 | 89 | $message = [ 90 | static::MSG_PREFIX, 91 | $curie, 92 | (string)$uri 93 | ]; 94 | return $this->send($message); 95 | } 96 | 97 | /** 98 | * Get the full request URI from the connection object if a prefix has been established for it 99 | * 100 | * @param string $uri 101 | * 102 | * @return string 103 | */ 104 | public function getUri($uri) 105 | { 106 | $prefix = $this->context->getPrefix($uri); 107 | return ($prefix ? $prefix : $uri); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | * @throws \Exception 113 | * @throws JsonException 114 | */ 115 | public function onMessage($msg) 116 | { 117 | if (null === ($json = @json_decode($msg, true))) { 118 | throw new \Exception("Can't to json_decode message: " . $msg); 119 | } 120 | 121 | if (!is_array($json) || $json !== array_values($json)) { 122 | throw new \UnexpectedValueException("Invalid WAMP message format"); 123 | } 124 | 125 | switch ($json[0]) { 126 | case static::MSG_PREFIX: 127 | $this->context->setPrefix($json[1], $json[2]); 128 | break; 129 | 130 | case static::MSG_CALL: 131 | array_shift($json); 132 | $callID = array_shift($json); 133 | $procURI = array_shift($json); 134 | 135 | if (count($json) == 1 && is_array($json[0])) { 136 | $json = $json[0]; 137 | } 138 | 139 | $this->context->call($callID, $procURI, $json); 140 | break; 141 | 142 | case static::MSG_SUBSCRIBE: 143 | $this->context->subscribe($this->getUri($json[1])); 144 | break; 145 | 146 | case static::MSG_UNSUBSCRIBE: 147 | $this->context->unsubscribe($this->getUri($json[1])); 148 | break; 149 | 150 | case static::MSG_PUBLISH: 151 | $exclude = (array_key_exists(3, $json) ? $json[3] : null); 152 | if (!is_array($exclude)) { 153 | if (true === (boolean)$exclude) { 154 | $exclude = [$this->context->getId()]; 155 | } else { 156 | $exclude = []; 157 | } 158 | } 159 | 160 | $eligible = (array_key_exists(4, $json) ? $json[4] : []); 161 | 162 | $this->context->publish($this->getUri($json[1]), $json[2], $exclude, $eligible); 163 | break; 164 | 165 | default: 166 | throw new Exception('Invalid message type'); 167 | } 168 | } 169 | 170 | public function result($callId, $data) 171 | { 172 | $message = [static::MSG_CALL_RESULT, $callId, $data]; 173 | return $this->context->send(json_encode($message)); 174 | } 175 | 176 | public function error($callId, $data) 177 | { 178 | $message = [static::MSG_CALL_ERROR, $callId, $data]; 179 | return $this->send($message); 180 | } 181 | 182 | public function send($message) 183 | { 184 | return $this->context->send($this->serialize($message)); 185 | } 186 | 187 | public function serialize($message) 188 | { 189 | return json_encode($message); 190 | } 191 | 192 | public function unserialize($message) 193 | { 194 | return json_decode($message); 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /src/wamp/assets/js/socketResource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('eg.components', []).service('server', function () { 4 | var prefix = 'http://' + document.domain + '/api/v1/'; 5 | var socketDefer = $.Deferred(); 6 | // WAMP session object 7 | 8 | var sess = $.extend(socketDefer.promise(), { 9 | call: function () { 10 | var args = arguments; 11 | var beforeOnOpenDefer = $.Deferred(); 12 | socketDefer.then(function () { 13 | sess.call.apply(sess, args).then(function (data) { 14 | beforeOnOpenDefer.resolve(data) 15 | }); 16 | }); 17 | return beforeOnOpenDefer; 18 | } 19 | }); 20 | 21 | function onConnect(session) { 22 | sess = session; 23 | socketDefer.resolve(); 24 | 25 | // sess._subscribe = sess.subscribe; 26 | // sess.subscribe = function (id, callback) { 27 | // sess._subscribe(id, function (data) { 28 | // $rootScope.$apply(function () { 29 | // callback && callback(data); 30 | // }); 31 | // }) 32 | // }; 33 | } 34 | 35 | function onDisconnect(code, reason) { 36 | sess = null; 37 | console.log("Connection lost (" + code + " " + reason + ")"); 38 | } 39 | 40 | // connect to WAMP server 41 | ab.connect('ws://' + document.domain + ':8047/', onConnect, onDisconnect, { 42 | 'maxRetries': 60000, 43 | 'retryDelay': 1000 44 | }); 45 | 46 | sess.prefix = prefix; 47 | return sess; 48 | }); 49 | 50 | angular.module('eg.components').factory('$socketResource', ['server', function (server) { 51 | /** 52 | * Create a shallow copy of an object and clear other fields from the destination 53 | */ 54 | function shallowClearAndCopy(src, dst) { 55 | dst = dst || {}; 56 | 57 | angular.forEach(dst, function (value, key) { 58 | delete dst[key]; 59 | }); 60 | 61 | for (var key in src) { 62 | if (src.hasOwnProperty(key) && key.charAt(0) !== '$' && key.charAt(1) !== '$') { 63 | dst[key] = src[key]; 64 | } 65 | } 66 | 67 | return dst; 68 | } 69 | 70 | 71 | //server.subscribe(prefix + "user", onEvent); 72 | 73 | function publishEvent() { 74 | sess.publish(prefix + "user", {a: "foo", b: "bar", c: 23}); 75 | } 76 | 77 | var DEFAULT_ACTIONS = { 78 | 'get': { 79 | url: 'view' 80 | }, 81 | 'save': { 82 | url: 'save' 83 | }, 84 | 'query': { 85 | url: 'index', 86 | isArray: true 87 | }, 88 | 'delete': { 89 | url: 'delete' 90 | } 91 | }; 92 | 93 | var noop = angular.noop, 94 | forEach = angular.forEach, 95 | extend = angular.extend, 96 | copy = angular.copy, 97 | isFunction = angular.isFunction; 98 | 99 | 100 | function Route(controller) { 101 | this.controller = controller; 102 | } 103 | 104 | Route.prototype = { 105 | url: function (action) { 106 | return server.prefix + this.controller + '/' + action; 107 | } 108 | }; 109 | 110 | function resourceFactory(url, paramDefaults, actions) { 111 | var route = new Route(url); 112 | 113 | function Resource(value) { 114 | shallowClearAndCopy(value || {}, this); 115 | } 116 | 117 | actions = extend({}, DEFAULT_ACTIONS, actions); 118 | 119 | forEach(actions, function (action, name) { 120 | 121 | var value = action.isArray ? [] : new Resource(); 122 | Resource[name] = function (url, params, callback) { 123 | if (!action.isArray) { 124 | params = params ? params : {}; 125 | if (typeof params !== 'object') { 126 | alert('params Must be object!'); 127 | } 128 | angular.forEach(this, function (value, key) { 129 | params[key] = angular.copy(value); 130 | }); 131 | } 132 | 133 | $.active++; 134 | var promise = server.call(route.url(url), params).then(function (response) { 135 | $.active--; 136 | 137 | var data = response.data, 138 | promise = value.$promise; 139 | 140 | if (data) { 141 | // Need to convert action.isArray to boolean in case it is undefined 142 | // jshint -W018 143 | if (angular.isArray(data) !== (!!action.isArray)) { 144 | throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + 145 | 'response to contain an {0} but got an {1}', 146 | action.isArray ? 'array' : 'object', angular.isArray(data) ? 'array' : 'object'); 147 | } 148 | // jshint +W018 149 | if (action.isArray) { 150 | value.length = 0; 151 | forEach(data, function (item) { 152 | value.push(new Resource(item)); 153 | }); 154 | } else { 155 | copy(data, value); 156 | value.$promise = promise; 157 | } 158 | } 159 | 160 | value.$resolved = true; 161 | 162 | response.resource = value; 163 | 164 | // $rootScope.$apply(function() { 165 | callback && callback(response) 166 | // }); 167 | 168 | return response; 169 | }); 170 | // we are creating instance / collection 171 | // - set the initial promise 172 | // - return the instance / collection 173 | value.$promise = promise; 174 | value.$resolved = false; 175 | return value; 176 | }; 177 | 178 | Resource.prototype['$' + name] = function (params, success, error) { 179 | if (isFunction(params)) { 180 | error = success; 181 | success = params; 182 | params = {}; 183 | } 184 | 185 | var result = Resource[name].call(this, action.url, params, success); 186 | return result.$promise || result; 187 | }; 188 | }); 189 | 190 | return Resource; 191 | } 192 | 193 | return resourceFactory; 194 | }]); 195 | -------------------------------------------------------------------------------- /src/wamp/assets/js/autobahn.js: -------------------------------------------------------------------------------- 1 | /** @license AutobahnJS - http://autobahn.ws 2 | * 3 | * Copyright 2011, 2012 Tavendo GmbH. 4 | * Licensed under the MIT License. 5 | * See license text at http://www.opensource.org/licenses/mit-license.php 6 | * 7 | * AutobahnJS includes code from: 8 | * 9 | * when - http://cujojs.com 10 | * 11 | * (c) copyright B Cavalier & J Hann 12 | * Licensed under the MIT License at: 13 | * http://www.opensource.org/licenses/mit-license.php 14 | * 15 | * Crypto-JS - http://code.google.com/p/crypto-js/ 16 | * 17 | * (c) 2009-2012 by Jeff Mott. All rights reserved. 18 | * Licensed under the New BSD License at: 19 | * http://code.google.com/p/crypto-js/wiki/License 20 | * 21 | * console-normalizer - https://github.com/Zenovations/console-normalizer 22 | * 23 | * (c) 2012 by Zenovations. 24 | * Licensed under the MIT License at: 25 | * http://www.opensource.org/licenses/mit-license.php 26 | * 27 | */ 28 | 29 | // needed to load when.js in legacy environments 30 | // https://github.com/cujojs/when 31 | if (!window.define) { 32 | window.define = function (factory) { 33 | try { 34 | delete window.define; 35 | } 36 | catch (e) { 37 | window.define = void 0; 38 | } // IE 39 | window.when = factory(); 40 | }; 41 | window.define.amd = {}; 42 | } 43 | 44 | 45 | (function(console) { 46 | /********************************************************************************************* 47 | * Make sure console exists because IE blows up if it's not open and you attempt to access it 48 | * Create some dummy functions if we need to, so we don't have to if/else everything 49 | *********************************************************************************************/ 50 | console||(console = window.console = { 51 | // all this "a, b, c, d, e" garbage is to make the IDEs happy, since they can't do variable argument lists 52 | /** 53 | * @param a 54 | * @param [b] 55 | * @param [c] 56 | * @param [d] 57 | * @param [e] 58 | */ 59 | log: function(a, b, c, d, e) {}, 60 | /** 61 | * @param a 62 | * @param [b] 63 | * @param [c] 64 | * @param [d] 65 | * @param [e] 66 | */ 67 | info: function(a, b, c, d, e) {}, 68 | /** 69 | * @param a 70 | * @param [b] 71 | * @param [c] 72 | * @param [d] 73 | * @param [e] 74 | */ 75 | warn: function(a, b, c, d, e) {}, 76 | /** 77 | * @param a 78 | * @param [b] 79 | * @param [c] 80 | * @param [d] 81 | * @param [e] 82 | */ 83 | error: function(a, b, c, d, e) {} 84 | }); 85 | 86 | // le sigh, IE, oh IE, how we fight... fix Function.prototype.bind as needed 87 | if (!Function.prototype.bind) { 88 | //credits: taken from bind_even_never in this discussion: https://prototype.lighthouseapp.com/projects/8886/tickets/215-optimize-bind-bindaseventlistener#ticket-215-9 89 | Function.prototype.bind = function(context) { 90 | var fn = this, args = Array.prototype.slice.call(arguments, 1); 91 | return function(){ 92 | return fn.apply(context, Array.prototype.concat.apply(args, arguments)); 93 | }; 94 | }; 95 | } 96 | 97 | // IE 9 won't allow us to call console.log.apply (WTF IE!) It also reports typeof(console.log) as 'object' (UNH!) 98 | // but together, those two errors can be useful in allowing us to fix stuff so it works right 99 | if( typeof(console.log) === 'object' ) { 100 | // Array.forEach doesn't work in IE 8 so don't try that :( 101 | console.log = Function.prototype.call.bind(console.log, console); 102 | console.info = Function.prototype.call.bind(console.info, console); 103 | console.warn = Function.prototype.call.bind(console.warn, console); 104 | console.error = Function.prototype.call.bind(console.error, console); 105 | } 106 | 107 | /** 108 | * Support group and groupEnd functions 109 | */ 110 | ('group' in console) || 111 | (console.group = function(msg) { 112 | console.info("\n--- "+msg+" ---\n"); 113 | }); 114 | ('groupEnd' in console) || 115 | (console.groupEnd = function() { 116 | console.log("\n"); 117 | }); 118 | 119 | /** 120 | * Support time and timeEnd functions 121 | */ 122 | ('time' in console) || 123 | (function() { 124 | var trackedTimes = {}; 125 | console.time = function(msg) { 126 | trackedTimes[msg] = new Date().getTime(); 127 | }; 128 | console.timeEnd = function(msg) { 129 | var end = new Date().getTime(), time = (msg in trackedTimes)? end - trackedTimes[msg] : 0; 130 | console.info(msg+': '+time+'ms') 131 | } 132 | }()); 133 | 134 | })(window.console); 135 | /** @license MIT License (c) copyright 2011-2013 original author or authors */ 136 | 137 | /** 138 | * A lightweight CommonJS Promises/A and when() implementation 139 | * when is part of the cujo.js family of libraries (http://cujojs.com/) 140 | * 141 | * Licensed under the MIT License at: 142 | * http://www.opensource.org/licenses/mit-license.php 143 | * 144 | * @author Brian Cavalier 145 | * @author John Hann 146 | * @version 2.7.0 147 | */ 148 | (function(define) { 'use strict'; 149 | define(function (require) { 150 | 151 | // Public API 152 | 153 | when.promise = promise; // Create a pending promise 154 | when.resolve = resolve; // Create a resolved promise 155 | when.reject = reject; // Create a rejected promise 156 | when.defer = defer; // Create a {promise, resolver} pair 157 | 158 | when.join = join; // Join 2 or more promises 159 | 160 | when.all = all; // Resolve a list of promises 161 | when.map = map; // Array.map() for promises 162 | when.reduce = reduce; // Array.reduce() for promises 163 | when.settle = settle; // Settle a list of promises 164 | 165 | when.any = any; // One-winner race 166 | when.some = some; // Multi-winner race 167 | 168 | when.isPromise = isPromiseLike; // DEPRECATED: use isPromiseLike 169 | when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable 170 | 171 | /** 172 | * Register an observer for a promise or immediate value. 173 | * 174 | * @param {*} promiseOrValue 175 | * @param {function?} [onFulfilled] callback to be called when promiseOrValue is 176 | * successfully fulfilled. If promiseOrValue is an immediate value, callback 177 | * will be invoked immediately. 178 | * @param {function?} [onRejected] callback to be called when promiseOrValue is 179 | * rejected. 180 | * @param {function?} [onProgress] callback to be called when progress updates 181 | * are issued for promiseOrValue. 182 | * @returns {Promise} a new {@link Promise} that will complete with the return 183 | * value of callback or errback or the completion value of promiseOrValue if 184 | * callback and/or errback is not supplied. 185 | */ 186 | function when(promiseOrValue, onFulfilled, onRejected, onProgress) { 187 | // Get a trusted promise for the input promiseOrValue, and then 188 | // register promise handlers 189 | return cast(promiseOrValue).then(onFulfilled, onRejected, onProgress); 190 | } 191 | 192 | function cast(x) { 193 | return x instanceof Promise ? x : resolve(x); 194 | } 195 | 196 | /** 197 | * Trusted Promise constructor. A Promise created from this constructor is 198 | * a trusted when.js promise. Any other duck-typed promise is considered 199 | * untrusted. 200 | * @constructor 201 | * @param {function} sendMessage function to deliver messages to the promise's handler 202 | * @param {function?} inspect function that reports the promise's state 203 | * @name Promise 204 | */ 205 | function Promise(sendMessage, inspect) { 206 | this._message = sendMessage; 207 | this.inspect = inspect; 208 | } 209 | 210 | var promisePrototype = Promise.prototype; 211 | 212 | /** 213 | * Register handlers for this promise. 214 | * @param [onFulfilled] {Function} fulfillment handler 215 | * @param [onRejected] {Function} rejection handler 216 | * @param [onProgress] {Function} progress handler 217 | * @return {Promise} new Promise 218 | */ 219 | promisePrototype.then = function(onFulfilled, onRejected, onProgress) { 220 | /*jshint unused:false*/ 221 | var args, sendMessage; 222 | 223 | args = arguments; 224 | sendMessage = this._message; 225 | 226 | return _promise(function(resolve, reject, notify) { 227 | sendMessage('when', args, resolve, notify); 228 | }, this._status && this._status.observed()); 229 | }; 230 | 231 | /** 232 | * Register a rejection handler. Shortcut for .then(undefined, onRejected) 233 | * @param {function?} onRejected 234 | * @return {Promise} 235 | */ 236 | promisePrototype['catch'] = promisePrototype.otherwise = function(onRejected) { 237 | return this.then(undef, onRejected); 238 | }; 239 | 240 | /** 241 | * Ensures that onFulfilledOrRejected will be called regardless of whether 242 | * this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT 243 | * receive the promises' value or reason. Any returned value will be disregarded. 244 | * onFulfilledOrRejected may throw or return a rejected promise to signal 245 | * an additional error. 246 | * @param {function} onFulfilledOrRejected handler to be called regardless of 247 | * fulfillment or rejection 248 | * @returns {Promise} 249 | */ 250 | promisePrototype['finally'] = promisePrototype.ensure = function(onFulfilledOrRejected) { 251 | return typeof onFulfilledOrRejected === 'function' 252 | ? this.then(injectHandler, injectHandler)['yield'](this) 253 | : this; 254 | 255 | function injectHandler() { 256 | return resolve(onFulfilledOrRejected()); 257 | } 258 | }; 259 | 260 | /** 261 | * Terminate a promise chain by handling the ultimate fulfillment value or 262 | * rejection reason, and assuming responsibility for all errors. if an 263 | * error propagates out of handleResult or handleFatalError, it will be 264 | * rethrown to the host, resulting in a loud stack track on most platforms 265 | * and a crash on some. 266 | * @param {function?} handleResult 267 | * @param {function?} handleError 268 | * @returns {undefined} 269 | */ 270 | promisePrototype.done = function(handleResult, handleError) { 271 | this.then(handleResult, handleError).otherwise(crash); 272 | }; 273 | 274 | /** 275 | * Shortcut for .then(function() { return value; }) 276 | * @param {*} value 277 | * @return {Promise} a promise that: 278 | * - is fulfilled if value is not a promise, or 279 | * - if value is a promise, will fulfill with its value, or reject 280 | * with its reason. 281 | */ 282 | promisePrototype['yield'] = function(value) { 283 | return this.then(function() { 284 | return value; 285 | }); 286 | }; 287 | 288 | /** 289 | * Runs a side effect when this promise fulfills, without changing the 290 | * fulfillment value. 291 | * @param {function} onFulfilledSideEffect 292 | * @returns {Promise} 293 | */ 294 | promisePrototype.tap = function(onFulfilledSideEffect) { 295 | return this.then(onFulfilledSideEffect)['yield'](this); 296 | }; 297 | 298 | /** 299 | * Assumes that this promise will fulfill with an array, and arranges 300 | * for the onFulfilled to be called with the array as its argument list 301 | * i.e. onFulfilled.apply(undefined, array). 302 | * @param {function} onFulfilled function to receive spread arguments 303 | * @return {Promise} 304 | */ 305 | promisePrototype.spread = function(onFulfilled) { 306 | return this.then(function(array) { 307 | // array may contain promises, so resolve its contents. 308 | return all(array, function(array) { 309 | return onFulfilled.apply(undef, array); 310 | }); 311 | }); 312 | }; 313 | 314 | /** 315 | * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected) 316 | * @deprecated 317 | */ 318 | promisePrototype.always = function(onFulfilledOrRejected, onProgress) { 319 | return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress); 320 | }; 321 | 322 | /** 323 | * Returns a resolved promise. The returned promise will be 324 | * - fulfilled with promiseOrValue if it is a value, or 325 | * - if promiseOrValue is a promise 326 | * - fulfilled with promiseOrValue's value after it is fulfilled 327 | * - rejected with promiseOrValue's reason after it is rejected 328 | * @param {*} value 329 | * @return {Promise} 330 | */ 331 | function resolve(value) { 332 | return promise(function(resolve) { 333 | resolve(value); 334 | }); 335 | } 336 | 337 | /** 338 | * Returns a rejected promise for the supplied promiseOrValue. The returned 339 | * promise will be rejected with: 340 | * - promiseOrValue, if it is a value, or 341 | * - if promiseOrValue is a promise 342 | * - promiseOrValue's value after it is fulfilled 343 | * - promiseOrValue's reason after it is rejected 344 | * @param {*} promiseOrValue the rejected value of the returned {@link Promise} 345 | * @return {Promise} rejected {@link Promise} 346 | */ 347 | function reject(promiseOrValue) { 348 | return when(promiseOrValue, rejected); 349 | } 350 | 351 | /** 352 | * Creates a {promise, resolver} pair, either or both of which 353 | * may be given out safely to consumers. 354 | * The resolver has resolve, reject, and progress. The promise 355 | * has then plus extended promise API. 356 | * 357 | * @return {{ 358 | * promise: Promise, 359 | * resolve: function:Promise, 360 | * reject: function:Promise, 361 | * notify: function:Promise 362 | * resolver: { 363 | * resolve: function:Promise, 364 | * reject: function:Promise, 365 | * notify: function:Promise 366 | * }}} 367 | */ 368 | function defer() { 369 | var deferred, pending, resolved; 370 | 371 | // Optimize object shape 372 | deferred = { 373 | promise: undef, resolve: undef, reject: undef, notify: undef, 374 | resolver: { resolve: undef, reject: undef, notify: undef } 375 | }; 376 | 377 | deferred.promise = pending = promise(makeDeferred); 378 | 379 | return deferred; 380 | 381 | function makeDeferred(resolvePending, rejectPending, notifyPending) { 382 | deferred.resolve = deferred.resolver.resolve = function(value) { 383 | if(resolved) { 384 | return resolve(value); 385 | } 386 | resolved = true; 387 | resolvePending(value); 388 | return pending; 389 | }; 390 | 391 | deferred.reject = deferred.resolver.reject = function(reason) { 392 | if(resolved) { 393 | return resolve(rejected(reason)); 394 | } 395 | resolved = true; 396 | rejectPending(reason); 397 | return pending; 398 | }; 399 | 400 | deferred.notify = deferred.resolver.notify = function(update) { 401 | notifyPending(update); 402 | return update; 403 | }; 404 | } 405 | } 406 | 407 | /** 408 | * Creates a new promise whose fate is determined by resolver. 409 | * @param {function} resolver function(resolve, reject, notify) 410 | * @returns {Promise} promise whose fate is determine by resolver 411 | */ 412 | function promise(resolver) { 413 | return _promise(resolver, monitorApi.PromiseStatus && monitorApi.PromiseStatus()); 414 | } 415 | 416 | /** 417 | * Creates a new promise, linked to parent, whose fate is determined 418 | * by resolver. 419 | * @param {function} resolver function(resolve, reject, notify) 420 | * @param {Promise?} status promise from which the new promise is begotten 421 | * @returns {Promise} promise whose fate is determine by resolver 422 | * @private 423 | */ 424 | function _promise(resolver, status) { 425 | var self, value, consumers = []; 426 | 427 | self = new Promise(_message, inspect); 428 | self._status = status; 429 | 430 | // Call the provider resolver to seal the promise's fate 431 | try { 432 | resolver(promiseResolve, promiseReject, promiseNotify); 433 | } catch(e) { 434 | promiseReject(e); 435 | } 436 | 437 | // Return the promise 438 | return self; 439 | 440 | /** 441 | * Private message delivery. Queues and delivers messages to 442 | * the promise's ultimate fulfillment value or rejection reason. 443 | * @private 444 | * @param {String} type 445 | * @param {Array} args 446 | * @param {Function} resolve 447 | * @param {Function} notify 448 | */ 449 | function _message(type, args, resolve, notify) { 450 | consumers ? consumers.push(deliver) : enqueue(function() { deliver(value); }); 451 | 452 | function deliver(p) { 453 | p._message(type, args, resolve, notify); 454 | } 455 | } 456 | 457 | /** 458 | * Returns a snapshot of the promise's state at the instant inspect() 459 | * is called. The returned object is not live and will not update as 460 | * the promise's state changes. 461 | * @returns {{ state:String, value?:*, reason?:* }} status snapshot 462 | * of the promise. 463 | */ 464 | function inspect() { 465 | return value ? value.inspect() : toPendingState(); 466 | } 467 | 468 | /** 469 | * Transition from pre-resolution state to post-resolution state, notifying 470 | * all listeners of the ultimate fulfillment or rejection 471 | * @param {*|Promise} val resolution value 472 | */ 473 | function promiseResolve(val) { 474 | if(!consumers) { 475 | return; 476 | } 477 | 478 | var queue = consumers; 479 | consumers = undef; 480 | 481 | enqueue(function () { 482 | value = coerce(self, val); 483 | if(status) { 484 | updateStatus(value, status); 485 | } 486 | runHandlers(queue, value); 487 | }); 488 | 489 | } 490 | 491 | /** 492 | * Reject this promise with the supplied reason, which will be used verbatim. 493 | * @param {*} reason reason for the rejection 494 | */ 495 | function promiseReject(reason) { 496 | promiseResolve(rejected(reason)); 497 | } 498 | 499 | /** 500 | * Issue a progress event, notifying all progress listeners 501 | * @param {*} update progress event payload to pass to all listeners 502 | */ 503 | function promiseNotify(update) { 504 | if(consumers) { 505 | var queue = consumers; 506 | enqueue(function () { 507 | runHandlers(queue, progressed(update)); 508 | }); 509 | } 510 | } 511 | } 512 | 513 | /** 514 | * Run a queue of functions as quickly as possible, passing 515 | * value to each. 516 | */ 517 | function runHandlers(queue, value) { 518 | for (var i = 0; i < queue.length; i++) { 519 | queue[i](value); 520 | } 521 | } 522 | 523 | /** 524 | * Creates a fulfilled, local promise as a proxy for a value 525 | * NOTE: must never be exposed 526 | * @param {*} value fulfillment value 527 | * @returns {Promise} 528 | */ 529 | function fulfilled(value) { 530 | return near( 531 | new NearFulfilledProxy(value), 532 | function() { return toFulfilledState(value); } 533 | ); 534 | } 535 | 536 | /** 537 | * Creates a rejected, local promise with the supplied reason 538 | * NOTE: must never be exposed 539 | * @param {*} reason rejection reason 540 | * @returns {Promise} 541 | */ 542 | function rejected(reason) { 543 | return near( 544 | new NearRejectedProxy(reason), 545 | function() { return toRejectedState(reason); } 546 | ); 547 | } 548 | 549 | /** 550 | * Creates a near promise using the provided proxy 551 | * NOTE: must never be exposed 552 | * @param {object} proxy proxy for the promise's ultimate value or reason 553 | * @param {function} inspect function that returns a snapshot of the 554 | * returned near promise's state 555 | * @returns {Promise} 556 | */ 557 | function near(proxy, inspect) { 558 | return new Promise(function (type, args, resolve) { 559 | try { 560 | resolve(proxy[type].apply(proxy, args)); 561 | } catch(e) { 562 | resolve(rejected(e)); 563 | } 564 | }, inspect); 565 | } 566 | 567 | /** 568 | * Create a progress promise with the supplied update. 569 | * @private 570 | * @param {*} update 571 | * @return {Promise} progress promise 572 | */ 573 | function progressed(update) { 574 | return new Promise(function (type, args, _, notify) { 575 | var onProgress = args[2]; 576 | try { 577 | notify(typeof onProgress === 'function' ? onProgress(update) : update); 578 | } catch(e) { 579 | notify(e); 580 | } 581 | }); 582 | } 583 | 584 | /** 585 | * Coerces x to a trusted Promise 586 | * @param {*} x thing to coerce 587 | * @returns {*} Guaranteed to return a trusted Promise. If x 588 | * is trusted, returns x, otherwise, returns a new, trusted, already-resolved 589 | * Promise whose resolution value is: 590 | * * the resolution value of x if it's a foreign promise, or 591 | * * x if it's a value 592 | */ 593 | function coerce(self, x) { 594 | if (x === self) { 595 | return rejected(new TypeError()); 596 | } 597 | 598 | if (x instanceof Promise) { 599 | return x; 600 | } 601 | 602 | try { 603 | var untrustedThen = x === Object(x) && x.then; 604 | 605 | return typeof untrustedThen === 'function' 606 | ? assimilate(untrustedThen, x) 607 | : fulfilled(x); 608 | } catch(e) { 609 | return rejected(e); 610 | } 611 | } 612 | 613 | /** 614 | * Safely assimilates a foreign thenable by wrapping it in a trusted promise 615 | * @param {function} untrustedThen x's then() method 616 | * @param {object|function} x thenable 617 | * @returns {Promise} 618 | */ 619 | function assimilate(untrustedThen, x) { 620 | return promise(function (resolve, reject) { 621 | fcall(untrustedThen, x, resolve, reject); 622 | }); 623 | } 624 | 625 | /** 626 | * Proxy for a near, fulfilled value 627 | * @param {*} value 628 | * @constructor 629 | */ 630 | function NearFulfilledProxy(value) { 631 | this.value = value; 632 | } 633 | 634 | NearFulfilledProxy.prototype.when = function(onResult) { 635 | return typeof onResult === 'function' ? onResult(this.value) : this.value; 636 | }; 637 | 638 | /** 639 | * Proxy for a near rejection 640 | * @param {*} reason 641 | * @constructor 642 | */ 643 | function NearRejectedProxy(reason) { 644 | this.reason = reason; 645 | } 646 | 647 | NearRejectedProxy.prototype.when = function(_, onError) { 648 | if(typeof onError === 'function') { 649 | return onError(this.reason); 650 | } else { 651 | throw this.reason; 652 | } 653 | }; 654 | 655 | function updateStatus(value, status) { 656 | value.then(statusFulfilled, statusRejected); 657 | 658 | function statusFulfilled() { status.fulfilled(); } 659 | function statusRejected(r) { status.rejected(r); } 660 | } 661 | 662 | /** 663 | * Determines if x is promise-like, i.e. a thenable object 664 | * NOTE: Will return true for *any thenable object*, and isn't truly 665 | * safe, since it may attempt to access the `then` property of x (i.e. 666 | * clever/malicious getters may do weird things) 667 | * @param {*} x anything 668 | * @returns {boolean} true if x is promise-like 669 | */ 670 | function isPromiseLike(x) { 671 | return x && typeof x.then === 'function'; 672 | } 673 | 674 | /** 675 | * Initiates a competitive race, returning a promise that will resolve when 676 | * howMany of the supplied promisesOrValues have resolved, or will reject when 677 | * it becomes impossible for howMany to resolve, for example, when 678 | * (promisesOrValues.length - howMany) + 1 input promises reject. 679 | * 680 | * @param {Array} promisesOrValues array of anything, may contain a mix 681 | * of promises and values 682 | * @param howMany {number} number of promisesOrValues to resolve 683 | * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() 684 | * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() 685 | * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() 686 | * @returns {Promise} promise that will resolve to an array of howMany values that 687 | * resolved first, or will reject with an array of 688 | * (promisesOrValues.length - howMany) + 1 rejection reasons. 689 | */ 690 | function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) { 691 | 692 | return when(promisesOrValues, function(promisesOrValues) { 693 | 694 | return promise(resolveSome).then(onFulfilled, onRejected, onProgress); 695 | 696 | function resolveSome(resolve, reject, notify) { 697 | var toResolve, toReject, values, reasons, fulfillOne, rejectOne, len, i; 698 | 699 | len = promisesOrValues.length >>> 0; 700 | 701 | toResolve = Math.max(0, Math.min(howMany, len)); 702 | values = []; 703 | 704 | toReject = (len - toResolve) + 1; 705 | reasons = []; 706 | 707 | // No items in the input, resolve immediately 708 | if (!toResolve) { 709 | resolve(values); 710 | 711 | } else { 712 | rejectOne = function(reason) { 713 | reasons.push(reason); 714 | if(!--toReject) { 715 | fulfillOne = rejectOne = identity; 716 | reject(reasons); 717 | } 718 | }; 719 | 720 | fulfillOne = function(val) { 721 | // This orders the values based on promise resolution order 722 | values.push(val); 723 | if (!--toResolve) { 724 | fulfillOne = rejectOne = identity; 725 | resolve(values); 726 | } 727 | }; 728 | 729 | for(i = 0; i < len; ++i) { 730 | if(i in promisesOrValues) { 731 | when(promisesOrValues[i], fulfiller, rejecter, notify); 732 | } 733 | } 734 | } 735 | 736 | function rejecter(reason) { 737 | rejectOne(reason); 738 | } 739 | 740 | function fulfiller(val) { 741 | fulfillOne(val); 742 | } 743 | } 744 | }); 745 | } 746 | 747 | /** 748 | * Initiates a competitive race, returning a promise that will resolve when 749 | * any one of the supplied promisesOrValues has resolved or will reject when 750 | * *all* promisesOrValues have rejected. 751 | * 752 | * @param {Array|Promise} promisesOrValues array of anything, may contain a mix 753 | * of {@link Promise}s and values 754 | * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() 755 | * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() 756 | * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() 757 | * @returns {Promise} promise that will resolve to the value that resolved first, or 758 | * will reject with an array of all rejected inputs. 759 | */ 760 | function any(promisesOrValues, onFulfilled, onRejected, onProgress) { 761 | 762 | function unwrapSingleResult(val) { 763 | return onFulfilled ? onFulfilled(val[0]) : val[0]; 764 | } 765 | 766 | return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress); 767 | } 768 | 769 | /** 770 | * Return a promise that will resolve only once all the supplied promisesOrValues 771 | * have resolved. The resolution value of the returned promise will be an array 772 | * containing the resolution values of each of the promisesOrValues. 773 | * @memberOf when 774 | * 775 | * @param {Array|Promise} promisesOrValues array of anything, may contain a mix 776 | * of {@link Promise}s and values 777 | * @param {function?} [onFulfilled] DEPRECATED, use returnedPromise.then() 778 | * @param {function?} [onRejected] DEPRECATED, use returnedPromise.then() 779 | * @param {function?} [onProgress] DEPRECATED, use returnedPromise.then() 780 | * @returns {Promise} 781 | */ 782 | function all(promisesOrValues, onFulfilled, onRejected, onProgress) { 783 | return _map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress); 784 | } 785 | 786 | /** 787 | * Joins multiple promises into a single returned promise. 788 | * @return {Promise} a promise that will fulfill when *all* the input promises 789 | * have fulfilled, or will reject when *any one* of the input promises rejects. 790 | */ 791 | function join(/* ...promises */) { 792 | return _map(arguments, identity); 793 | } 794 | 795 | /** 796 | * Settles all input promises such that they are guaranteed not to 797 | * be pending once the returned promise fulfills. The returned promise 798 | * will always fulfill, except in the case where `array` is a promise 799 | * that rejects. 800 | * @param {Array|Promise} array or promise for array of promises to settle 801 | * @returns {Promise} promise that always fulfills with an array of 802 | * outcome snapshots for each input promise. 803 | */ 804 | function settle(array) { 805 | return _map(array, toFulfilledState, toRejectedState); 806 | } 807 | 808 | /** 809 | * Promise-aware array map function, similar to `Array.prototype.map()`, 810 | * but input array may contain promises or values. 811 | * @param {Array|Promise} array array of anything, may contain promises and values 812 | * @param {function} mapFunc map function which may return a promise or value 813 | * @returns {Promise} promise that will fulfill with an array of mapped values 814 | * or reject if any input promise rejects. 815 | */ 816 | function map(array, mapFunc) { 817 | return _map(array, mapFunc); 818 | } 819 | 820 | /** 821 | * Internal map that allows a fallback to handle rejections 822 | * @param {Array|Promise} array array of anything, may contain promises and values 823 | * @param {function} mapFunc map function which may return a promise or value 824 | * @param {function?} fallback function to handle rejected promises 825 | * @returns {Promise} promise that will fulfill with an array of mapped values 826 | * or reject if any input promise rejects. 827 | */ 828 | function _map(array, mapFunc, fallback) { 829 | return when(array, function(array) { 830 | 831 | return _promise(resolveMap); 832 | 833 | function resolveMap(resolve, reject, notify) { 834 | var results, len, toResolve, i; 835 | 836 | // Since we know the resulting length, we can preallocate the results 837 | // array to avoid array expansions. 838 | toResolve = len = array.length >>> 0; 839 | results = []; 840 | 841 | if(!toResolve) { 842 | resolve(results); 843 | return; 844 | } 845 | 846 | // Since mapFunc may be async, get all invocations of it into flight 847 | for(i = 0; i < len; i++) { 848 | if(i in array) { 849 | resolveOne(array[i], i); 850 | } else { 851 | --toResolve; 852 | } 853 | } 854 | 855 | function resolveOne(item, i) { 856 | when(item, mapFunc, fallback).then(function(mapped) { 857 | results[i] = mapped; 858 | 859 | if(!--toResolve) { 860 | resolve(results); 861 | } 862 | }, reject, notify); 863 | } 864 | } 865 | }); 866 | } 867 | 868 | /** 869 | * Traditional reduce function, similar to `Array.prototype.reduce()`, but 870 | * input may contain promises and/or values, and reduceFunc 871 | * may return either a value or a promise, *and* initialValue may 872 | * be a promise for the starting value. 873 | * 874 | * @param {Array|Promise} promise array or promise for an array of anything, 875 | * may contain a mix of promises and values. 876 | * @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total), 877 | * where total is the total number of items being reduced, and will be the same 878 | * in each call to reduceFunc. 879 | * @returns {Promise} that will resolve to the final reduced value 880 | */ 881 | function reduce(promise, reduceFunc /*, initialValue */) { 882 | var args = fcall(slice, arguments, 1); 883 | 884 | return when(promise, function(array) { 885 | var total; 886 | 887 | total = array.length; 888 | 889 | // Wrap the supplied reduceFunc with one that handles promises and then 890 | // delegates to the supplied. 891 | args[0] = function (current, val, i) { 892 | return when(current, function (c) { 893 | return when(val, function (value) { 894 | return reduceFunc(c, value, i, total); 895 | }); 896 | }); 897 | }; 898 | 899 | return reduceArray.apply(array, args); 900 | }); 901 | } 902 | 903 | // Snapshot states 904 | 905 | /** 906 | * Creates a fulfilled state snapshot 907 | * @private 908 | * @param {*} x any value 909 | * @returns {{state:'fulfilled',value:*}} 910 | */ 911 | function toFulfilledState(x) { 912 | return { state: 'fulfilled', value: x }; 913 | } 914 | 915 | /** 916 | * Creates a rejected state snapshot 917 | * @private 918 | * @param {*} x any reason 919 | * @returns {{state:'rejected',reason:*}} 920 | */ 921 | function toRejectedState(x) { 922 | return { state: 'rejected', reason: x }; 923 | } 924 | 925 | /** 926 | * Creates a pending state snapshot 927 | * @private 928 | * @returns {{state:'pending'}} 929 | */ 930 | function toPendingState() { 931 | return { state: 'pending' }; 932 | } 933 | 934 | // 935 | // Internals, utilities, etc. 936 | // 937 | 938 | var reduceArray, slice, fcall, nextTick, handlerQueue, 939 | funcProto, call, arrayProto, monitorApi, 940 | capturedSetTimeout, cjsRequire, MutationObs, undef; 941 | 942 | cjsRequire = require; 943 | 944 | // 945 | // Shared handler queue processing 946 | // 947 | // Credit to Twisol (https://github.com/Twisol) for suggesting 948 | // this type of extensible queue + trampoline approach for 949 | // next-tick conflation. 950 | 951 | handlerQueue = []; 952 | 953 | /** 954 | * Enqueue a task. If the queue is not currently scheduled to be 955 | * drained, schedule it. 956 | * @param {function} task 957 | */ 958 | function enqueue(task) { 959 | if(handlerQueue.push(task) === 1) { 960 | nextTick(drainQueue); 961 | } 962 | } 963 | 964 | /** 965 | * Drain the handler queue entirely, being careful to allow the 966 | * queue to be extended while it is being processed, and to continue 967 | * processing until it is truly empty. 968 | */ 969 | function drainQueue() { 970 | runHandlers(handlerQueue); 971 | handlerQueue = []; 972 | } 973 | 974 | // Allow attaching the monitor to when() if env has no console 975 | monitorApi = typeof console !== 'undefined' ? console : when; 976 | 977 | // Sniff "best" async scheduling option 978 | // Prefer process.nextTick or MutationObserver, then check for 979 | // vertx and finally fall back to setTimeout 980 | /*global process,document,setTimeout,MutationObserver,WebKitMutationObserver*/ 981 | if (typeof process === 'object' && process.nextTick) { 982 | nextTick = process.nextTick; 983 | } else if(MutationObs = 984 | (typeof MutationObserver === 'function' && MutationObserver) || 985 | (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver)) { 986 | nextTick = (function(document, MutationObserver, drainQueue) { 987 | var el = document.createElement('div'); 988 | new MutationObserver(drainQueue).observe(el, { attributes: true }); 989 | 990 | return function() { 991 | el.setAttribute('x', 'x'); 992 | }; 993 | }(document, MutationObs, drainQueue)); 994 | } else { 995 | try { 996 | // vert.x 1.x || 2.x 997 | nextTick = cjsRequire('vertx').runOnLoop || cjsRequire('vertx').runOnContext; 998 | } catch(ignore) { 999 | // capture setTimeout to avoid being caught by fake timers 1000 | // used in time based tests 1001 | capturedSetTimeout = setTimeout; 1002 | nextTick = function(t) { capturedSetTimeout(t, 0); }; 1003 | } 1004 | } 1005 | 1006 | // 1007 | // Capture/polyfill function and array utils 1008 | // 1009 | 1010 | // Safe function calls 1011 | funcProto = Function.prototype; 1012 | call = funcProto.call; 1013 | fcall = funcProto.bind 1014 | ? call.bind(call) 1015 | : function(f, context) { 1016 | return f.apply(context, slice.call(arguments, 2)); 1017 | }; 1018 | 1019 | // Safe array ops 1020 | arrayProto = []; 1021 | slice = arrayProto.slice; 1022 | 1023 | // ES5 reduce implementation if native not available 1024 | // See: http://es5.github.com/#x15.4.4.21 as there are many 1025 | // specifics and edge cases. ES5 dictates that reduce.length === 1 1026 | // This implementation deviates from ES5 spec in the following ways: 1027 | // 1. It does not check if reduceFunc is a Callable 1028 | reduceArray = arrayProto.reduce || 1029 | function(reduceFunc /*, initialValue */) { 1030 | /*jshint maxcomplexity: 7*/ 1031 | var arr, args, reduced, len, i; 1032 | 1033 | i = 0; 1034 | arr = Object(this); 1035 | len = arr.length >>> 0; 1036 | args = arguments; 1037 | 1038 | // If no initialValue, use first item of array (we know length !== 0 here) 1039 | // and adjust i to start at second item 1040 | if(args.length <= 1) { 1041 | // Skip to the first real element in the array 1042 | for(;;) { 1043 | if(i in arr) { 1044 | reduced = arr[i++]; 1045 | break; 1046 | } 1047 | 1048 | // If we reached the end of the array without finding any real 1049 | // elements, it's a TypeError 1050 | if(++i >= len) { 1051 | throw new TypeError(); 1052 | } 1053 | } 1054 | } else { 1055 | // If initialValue provided, use it 1056 | reduced = args[1]; 1057 | } 1058 | 1059 | // Do the actual reduce 1060 | for(;i < len; ++i) { 1061 | if(i in arr) { 1062 | reduced = reduceFunc(reduced, arr[i], i, arr); 1063 | } 1064 | } 1065 | 1066 | return reduced; 1067 | }; 1068 | 1069 | function identity(x) { 1070 | return x; 1071 | } 1072 | 1073 | function crash(fatalError) { 1074 | if(typeof monitorApi.reportUnhandled === 'function') { 1075 | monitorApi.reportUnhandled(); 1076 | } else { 1077 | enqueue(function() { 1078 | throw fatalError; 1079 | }); 1080 | } 1081 | 1082 | throw fatalError; 1083 | } 1084 | 1085 | return when; 1086 | }); 1087 | })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); 1088 | 1089 | /* 1090 | CryptoJS v3.1.2 1091 | code.google.com/p/crypto-js 1092 | (c) 2009-2013 by Jeff Mott. All rights reserved. 1093 | code.google.com/p/crypto-js/wiki/License 1094 | */ 1095 | /** 1096 | * CryptoJS core components. 1097 | */ 1098 | var CryptoJS = CryptoJS || (function (Math, undefined) { 1099 | /** 1100 | * CryptoJS namespace. 1101 | */ 1102 | var C = {}; 1103 | 1104 | /** 1105 | * Library namespace. 1106 | */ 1107 | var C_lib = C.lib = {}; 1108 | 1109 | /** 1110 | * Base object for prototypal inheritance. 1111 | */ 1112 | var Base = C_lib.Base = (function () { 1113 | function F() {} 1114 | 1115 | return { 1116 | /** 1117 | * Creates a new object that inherits from this object. 1118 | * 1119 | * @param {Object} overrides Properties to copy into the new object. 1120 | * 1121 | * @return {Object} The new object. 1122 | * 1123 | * @static 1124 | * 1125 | * @example 1126 | * 1127 | * var MyType = CryptoJS.lib.Base.extend({ 1128 | * field: 'value', 1129 | * 1130 | * method: function () { 1131 | * } 1132 | * }); 1133 | */ 1134 | extend: function (overrides) { 1135 | // Spawn 1136 | F.prototype = this; 1137 | var subtype = new F(); 1138 | 1139 | // Augment 1140 | if (overrides) { 1141 | subtype.mixIn(overrides); 1142 | } 1143 | 1144 | // Create default initializer 1145 | if (!subtype.hasOwnProperty('init')) { 1146 | subtype.init = function () { 1147 | subtype.$super.init.apply(this, arguments); 1148 | }; 1149 | } 1150 | 1151 | // Initializer's prototype is the subtype object 1152 | subtype.init.prototype = subtype; 1153 | 1154 | // Reference supertype 1155 | subtype.$super = this; 1156 | 1157 | return subtype; 1158 | }, 1159 | 1160 | /** 1161 | * Extends this object and runs the init method. 1162 | * Arguments to create() will be passed to init(). 1163 | * 1164 | * @return {Object} The new object. 1165 | * 1166 | * @static 1167 | * 1168 | * @example 1169 | * 1170 | * var instance = MyType.create(); 1171 | */ 1172 | create: function () { 1173 | var instance = this.extend(); 1174 | instance.init.apply(instance, arguments); 1175 | 1176 | return instance; 1177 | }, 1178 | 1179 | /** 1180 | * Initializes a newly created object. 1181 | * Override this method to add some logic when your objects are created. 1182 | * 1183 | * @example 1184 | * 1185 | * var MyType = CryptoJS.lib.Base.extend({ 1186 | * init: function () { 1187 | * // ... 1188 | * } 1189 | * }); 1190 | */ 1191 | init: function () { 1192 | }, 1193 | 1194 | /** 1195 | * Copies properties into this object. 1196 | * 1197 | * @param {Object} properties The properties to mix in. 1198 | * 1199 | * @example 1200 | * 1201 | * MyType.mixIn({ 1202 | * field: 'value' 1203 | * }); 1204 | */ 1205 | mixIn: function (properties) { 1206 | for (var propertyName in properties) { 1207 | if (properties.hasOwnProperty(propertyName)) { 1208 | this[propertyName] = properties[propertyName]; 1209 | } 1210 | } 1211 | 1212 | // IE won't copy toString using the loop above 1213 | if (properties.hasOwnProperty('toString')) { 1214 | this.toString = properties.toString; 1215 | } 1216 | }, 1217 | 1218 | /** 1219 | * Creates a copy of this object. 1220 | * 1221 | * @return {Object} The clone. 1222 | * 1223 | * @example 1224 | * 1225 | * var clone = instance.clone(); 1226 | */ 1227 | clone: function () { 1228 | return this.init.prototype.extend(this); 1229 | } 1230 | }; 1231 | }()); 1232 | 1233 | /** 1234 | * An array of 32-bit words. 1235 | * 1236 | * @property {Array} words The array of 32-bit words. 1237 | * @property {number} sigBytes The number of significant bytes in this word array. 1238 | */ 1239 | var WordArray = C_lib.WordArray = Base.extend({ 1240 | /** 1241 | * Initializes a newly created word array. 1242 | * 1243 | * @param {Array} words (Optional) An array of 32-bit words. 1244 | * @param {number} sigBytes (Optional) The number of significant bytes in the words. 1245 | * 1246 | * @example 1247 | * 1248 | * var wordArray = CryptoJS.lib.WordArray.create(); 1249 | * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); 1250 | * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); 1251 | */ 1252 | init: function (words, sigBytes) { 1253 | words = this.words = words || []; 1254 | 1255 | if (sigBytes != undefined) { 1256 | this.sigBytes = sigBytes; 1257 | } else { 1258 | this.sigBytes = words.length * 4; 1259 | } 1260 | }, 1261 | 1262 | /** 1263 | * Converts this word array to a string. 1264 | * 1265 | * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex 1266 | * 1267 | * @return {string} The stringified word array. 1268 | * 1269 | * @example 1270 | * 1271 | * var string = wordArray + ''; 1272 | * var string = wordArray.toString(); 1273 | * var string = wordArray.toString(CryptoJS.enc.Utf8); 1274 | */ 1275 | toString: function (encoder) { 1276 | return (encoder || Hex).stringify(this); 1277 | }, 1278 | 1279 | /** 1280 | * Concatenates a word array to this word array. 1281 | * 1282 | * @param {WordArray} wordArray The word array to append. 1283 | * 1284 | * @return {WordArray} This word array. 1285 | * 1286 | * @example 1287 | * 1288 | * wordArray1.concat(wordArray2); 1289 | */ 1290 | concat: function (wordArray) { 1291 | // Shortcuts 1292 | var thisWords = this.words; 1293 | var thatWords = wordArray.words; 1294 | var thisSigBytes = this.sigBytes; 1295 | var thatSigBytes = wordArray.sigBytes; 1296 | 1297 | // Clamp excess bits 1298 | this.clamp(); 1299 | 1300 | // Concat 1301 | if (thisSigBytes % 4) { 1302 | // Copy one byte at a time 1303 | for (var i = 0; i < thatSigBytes; i++) { 1304 | var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 1305 | thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); 1306 | } 1307 | } else if (thatWords.length > 0xffff) { 1308 | // Copy one word at a time 1309 | for (var i = 0; i < thatSigBytes; i += 4) { 1310 | thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; 1311 | } 1312 | } else { 1313 | // Copy all words at once 1314 | thisWords.push.apply(thisWords, thatWords); 1315 | } 1316 | this.sigBytes += thatSigBytes; 1317 | 1318 | // Chainable 1319 | return this; 1320 | }, 1321 | 1322 | /** 1323 | * Removes insignificant bits. 1324 | * 1325 | * @example 1326 | * 1327 | * wordArray.clamp(); 1328 | */ 1329 | clamp: function () { 1330 | // Shortcuts 1331 | var words = this.words; 1332 | var sigBytes = this.sigBytes; 1333 | 1334 | // Clamp 1335 | words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); 1336 | words.length = Math.ceil(sigBytes / 4); 1337 | }, 1338 | 1339 | /** 1340 | * Creates a copy of this word array. 1341 | * 1342 | * @return {WordArray} The clone. 1343 | * 1344 | * @example 1345 | * 1346 | * var clone = wordArray.clone(); 1347 | */ 1348 | clone: function () { 1349 | var clone = Base.clone.call(this); 1350 | clone.words = this.words.slice(0); 1351 | 1352 | return clone; 1353 | }, 1354 | 1355 | /** 1356 | * Creates a word array filled with random bytes. 1357 | * 1358 | * @param {number} nBytes The number of random bytes to generate. 1359 | * 1360 | * @return {WordArray} The random word array. 1361 | * 1362 | * @static 1363 | * 1364 | * @example 1365 | * 1366 | * var wordArray = CryptoJS.lib.WordArray.random(16); 1367 | */ 1368 | random: function (nBytes) { 1369 | var words = []; 1370 | for (var i = 0; i < nBytes; i += 4) { 1371 | words.push((Math.random() * 0x100000000) | 0); 1372 | } 1373 | 1374 | return new WordArray.init(words, nBytes); 1375 | } 1376 | }); 1377 | 1378 | /** 1379 | * Encoder namespace. 1380 | */ 1381 | var C_enc = C.enc = {}; 1382 | 1383 | /** 1384 | * Hex encoding strategy. 1385 | */ 1386 | var Hex = C_enc.Hex = { 1387 | /** 1388 | * Converts a word array to a hex string. 1389 | * 1390 | * @param {WordArray} wordArray The word array. 1391 | * 1392 | * @return {string} The hex string. 1393 | * 1394 | * @static 1395 | * 1396 | * @example 1397 | * 1398 | * var hexString = CryptoJS.enc.Hex.stringify(wordArray); 1399 | */ 1400 | stringify: function (wordArray) { 1401 | // Shortcuts 1402 | var words = wordArray.words; 1403 | var sigBytes = wordArray.sigBytes; 1404 | 1405 | // Convert 1406 | var hexChars = []; 1407 | for (var i = 0; i < sigBytes; i++) { 1408 | var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 1409 | hexChars.push((bite >>> 4).toString(16)); 1410 | hexChars.push((bite & 0x0f).toString(16)); 1411 | } 1412 | 1413 | return hexChars.join(''); 1414 | }, 1415 | 1416 | /** 1417 | * Converts a hex string to a word array. 1418 | * 1419 | * @param {string} hexStr The hex string. 1420 | * 1421 | * @return {WordArray} The word array. 1422 | * 1423 | * @static 1424 | * 1425 | * @example 1426 | * 1427 | * var wordArray = CryptoJS.enc.Hex.parse(hexString); 1428 | */ 1429 | parse: function (hexStr) { 1430 | // Shortcut 1431 | var hexStrLength = hexStr.length; 1432 | 1433 | // Convert 1434 | var words = []; 1435 | for (var i = 0; i < hexStrLength; i += 2) { 1436 | words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); 1437 | } 1438 | 1439 | return new WordArray.init(words, hexStrLength / 2); 1440 | } 1441 | }; 1442 | 1443 | /** 1444 | * Latin1 encoding strategy. 1445 | */ 1446 | var Latin1 = C_enc.Latin1 = { 1447 | /** 1448 | * Converts a word array to a Latin1 string. 1449 | * 1450 | * @param {WordArray} wordArray The word array. 1451 | * 1452 | * @return {string} The Latin1 string. 1453 | * 1454 | * @static 1455 | * 1456 | * @example 1457 | * 1458 | * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); 1459 | */ 1460 | stringify: function (wordArray) { 1461 | // Shortcuts 1462 | var words = wordArray.words; 1463 | var sigBytes = wordArray.sigBytes; 1464 | 1465 | // Convert 1466 | var latin1Chars = []; 1467 | for (var i = 0; i < sigBytes; i++) { 1468 | var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 1469 | latin1Chars.push(String.fromCharCode(bite)); 1470 | } 1471 | 1472 | return latin1Chars.join(''); 1473 | }, 1474 | 1475 | /** 1476 | * Converts a Latin1 string to a word array. 1477 | * 1478 | * @param {string} latin1Str The Latin1 string. 1479 | * 1480 | * @return {WordArray} The word array. 1481 | * 1482 | * @static 1483 | * 1484 | * @example 1485 | * 1486 | * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); 1487 | */ 1488 | parse: function (latin1Str) { 1489 | // Shortcut 1490 | var latin1StrLength = latin1Str.length; 1491 | 1492 | // Convert 1493 | var words = []; 1494 | for (var i = 0; i < latin1StrLength; i++) { 1495 | words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); 1496 | } 1497 | 1498 | return new WordArray.init(words, latin1StrLength); 1499 | } 1500 | }; 1501 | 1502 | /** 1503 | * UTF-8 encoding strategy. 1504 | */ 1505 | var Utf8 = C_enc.Utf8 = { 1506 | /** 1507 | * Converts a word array to a UTF-8 string. 1508 | * 1509 | * @param {WordArray} wordArray The word array. 1510 | * 1511 | * @return {string} The UTF-8 string. 1512 | * 1513 | * @static 1514 | * 1515 | * @example 1516 | * 1517 | * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); 1518 | */ 1519 | stringify: function (wordArray) { 1520 | try { 1521 | return decodeURIComponent(escape(Latin1.stringify(wordArray))); 1522 | } catch (e) { 1523 | throw new Error('Malformed UTF-8 data'); 1524 | } 1525 | }, 1526 | 1527 | /** 1528 | * Converts a UTF-8 string to a word array. 1529 | * 1530 | * @param {string} utf8Str The UTF-8 string. 1531 | * 1532 | * @return {WordArray} The word array. 1533 | * 1534 | * @static 1535 | * 1536 | * @example 1537 | * 1538 | * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); 1539 | */ 1540 | parse: function (utf8Str) { 1541 | return Latin1.parse(unescape(encodeURIComponent(utf8Str))); 1542 | } 1543 | }; 1544 | 1545 | /** 1546 | * Abstract buffered block algorithm template. 1547 | * 1548 | * The property blockSize must be implemented in a concrete subtype. 1549 | * 1550 | * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 1551 | */ 1552 | var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ 1553 | /** 1554 | * Resets this block algorithm's data buffer to its initial state. 1555 | * 1556 | * @example 1557 | * 1558 | * bufferedBlockAlgorithm.reset(); 1559 | */ 1560 | reset: function () { 1561 | // Initial values 1562 | this._data = new WordArray.init(); 1563 | this._nDataBytes = 0; 1564 | }, 1565 | 1566 | /** 1567 | * Adds new data to this block algorithm's buffer. 1568 | * 1569 | * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. 1570 | * 1571 | * @example 1572 | * 1573 | * bufferedBlockAlgorithm._append('data'); 1574 | * bufferedBlockAlgorithm._append(wordArray); 1575 | */ 1576 | _append: function (data) { 1577 | // Convert string to WordArray, else assume WordArray already 1578 | if (typeof data == 'string') { 1579 | data = Utf8.parse(data); 1580 | } 1581 | 1582 | // Append 1583 | this._data.concat(data); 1584 | this._nDataBytes += data.sigBytes; 1585 | }, 1586 | 1587 | /** 1588 | * Processes available data blocks. 1589 | * 1590 | * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. 1591 | * 1592 | * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. 1593 | * 1594 | * @return {WordArray} The processed data. 1595 | * 1596 | * @example 1597 | * 1598 | * var processedData = bufferedBlockAlgorithm._process(); 1599 | * var processedData = bufferedBlockAlgorithm._process(!!'flush'); 1600 | */ 1601 | _process: function (doFlush) { 1602 | // Shortcuts 1603 | var data = this._data; 1604 | var dataWords = data.words; 1605 | var dataSigBytes = data.sigBytes; 1606 | var blockSize = this.blockSize; 1607 | var blockSizeBytes = blockSize * 4; 1608 | 1609 | // Count blocks ready 1610 | var nBlocksReady = dataSigBytes / blockSizeBytes; 1611 | if (doFlush) { 1612 | // Round up to include partial blocks 1613 | nBlocksReady = Math.ceil(nBlocksReady); 1614 | } else { 1615 | // Round down to include only full blocks, 1616 | // less the number of blocks that must remain in the buffer 1617 | nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); 1618 | } 1619 | 1620 | // Count words ready 1621 | var nWordsReady = nBlocksReady * blockSize; 1622 | 1623 | // Count bytes ready 1624 | var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); 1625 | 1626 | // Process blocks 1627 | if (nWordsReady) { 1628 | for (var offset = 0; offset < nWordsReady; offset += blockSize) { 1629 | // Perform concrete-algorithm logic 1630 | this._doProcessBlock(dataWords, offset); 1631 | } 1632 | 1633 | // Remove processed words 1634 | var processedWords = dataWords.splice(0, nWordsReady); 1635 | data.sigBytes -= nBytesReady; 1636 | } 1637 | 1638 | // Return processed words 1639 | return new WordArray.init(processedWords, nBytesReady); 1640 | }, 1641 | 1642 | /** 1643 | * Creates a copy of this object. 1644 | * 1645 | * @return {Object} The clone. 1646 | * 1647 | * @example 1648 | * 1649 | * var clone = bufferedBlockAlgorithm.clone(); 1650 | */ 1651 | clone: function () { 1652 | var clone = Base.clone.call(this); 1653 | clone._data = this._data.clone(); 1654 | 1655 | return clone; 1656 | }, 1657 | 1658 | _minBufferSize: 0 1659 | }); 1660 | 1661 | /** 1662 | * Abstract hasher template. 1663 | * 1664 | * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) 1665 | */ 1666 | var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ 1667 | /** 1668 | * Configuration options. 1669 | */ 1670 | cfg: Base.extend(), 1671 | 1672 | /** 1673 | * Initializes a newly created hasher. 1674 | * 1675 | * @param {Object} cfg (Optional) The configuration options to use for this hash computation. 1676 | * 1677 | * @example 1678 | * 1679 | * var hasher = CryptoJS.algo.SHA256.create(); 1680 | */ 1681 | init: function (cfg) { 1682 | // Apply config defaults 1683 | this.cfg = this.cfg.extend(cfg); 1684 | 1685 | // Set initial values 1686 | this.reset(); 1687 | }, 1688 | 1689 | /** 1690 | * Resets this hasher to its initial state. 1691 | * 1692 | * @example 1693 | * 1694 | * hasher.reset(); 1695 | */ 1696 | reset: function () { 1697 | // Reset data buffer 1698 | BufferedBlockAlgorithm.reset.call(this); 1699 | 1700 | // Perform concrete-hasher logic 1701 | this._doReset(); 1702 | }, 1703 | 1704 | /** 1705 | * Updates this hasher with a message. 1706 | * 1707 | * @param {WordArray|string} messageUpdate The message to append. 1708 | * 1709 | * @return {Hasher} This hasher. 1710 | * 1711 | * @example 1712 | * 1713 | * hasher.update('message'); 1714 | * hasher.update(wordArray); 1715 | */ 1716 | update: function (messageUpdate) { 1717 | // Append 1718 | this._append(messageUpdate); 1719 | 1720 | // Update the hash 1721 | this._process(); 1722 | 1723 | // Chainable 1724 | return this; 1725 | }, 1726 | 1727 | /** 1728 | * Finalizes the hash computation. 1729 | * Note that the finalize operation is effectively a destructive, read-once operation. 1730 | * 1731 | * @param {WordArray|string} messageUpdate (Optional) A final message update. 1732 | * 1733 | * @return {WordArray} The hash. 1734 | * 1735 | * @example 1736 | * 1737 | * var hash = hasher.finalize(); 1738 | * var hash = hasher.finalize('message'); 1739 | * var hash = hasher.finalize(wordArray); 1740 | */ 1741 | finalize: function (messageUpdate) { 1742 | // Final message update 1743 | if (messageUpdate) { 1744 | this._append(messageUpdate); 1745 | } 1746 | 1747 | // Perform concrete-hasher logic 1748 | var hash = this._doFinalize(); 1749 | 1750 | return hash; 1751 | }, 1752 | 1753 | blockSize: 512/32, 1754 | 1755 | /** 1756 | * Creates a shortcut function to a hasher's object interface. 1757 | * 1758 | * @param {Hasher} hasher The hasher to create a helper for. 1759 | * 1760 | * @return {Function} The shortcut function. 1761 | * 1762 | * @static 1763 | * 1764 | * @example 1765 | * 1766 | * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); 1767 | */ 1768 | _createHelper: function (hasher) { 1769 | return function (message, cfg) { 1770 | return new hasher.init(cfg).finalize(message); 1771 | }; 1772 | }, 1773 | 1774 | /** 1775 | * Creates a shortcut function to the HMAC's object interface. 1776 | * 1777 | * @param {Hasher} hasher The hasher to use in this HMAC helper. 1778 | * 1779 | * @return {Function} The shortcut function. 1780 | * 1781 | * @static 1782 | * 1783 | * @example 1784 | * 1785 | * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); 1786 | */ 1787 | _createHmacHelper: function (hasher) { 1788 | return function (message, key) { 1789 | return new C_algo.HMAC.init(hasher, key).finalize(message); 1790 | }; 1791 | } 1792 | }); 1793 | 1794 | /** 1795 | * Algorithm namespace. 1796 | */ 1797 | var C_algo = C.algo = {}; 1798 | 1799 | return C; 1800 | }(Math)); 1801 | 1802 | /* 1803 | CryptoJS v3.1.2 1804 | code.google.com/p/crypto-js 1805 | (c) 2009-2013 by Jeff Mott. All rights reserved. 1806 | code.google.com/p/crypto-js/wiki/License 1807 | */ 1808 | (function () { 1809 | // Shortcuts 1810 | var C = CryptoJS; 1811 | var C_lib = C.lib; 1812 | var WordArray = C_lib.WordArray; 1813 | var C_enc = C.enc; 1814 | 1815 | /** 1816 | * Base64 encoding strategy. 1817 | */ 1818 | var Base64 = C_enc.Base64 = { 1819 | /** 1820 | * Converts a word array to a Base64 string. 1821 | * 1822 | * @param {WordArray} wordArray The word array. 1823 | * 1824 | * @return {string} The Base64 string. 1825 | * 1826 | * @static 1827 | * 1828 | * @example 1829 | * 1830 | * var base64String = CryptoJS.enc.Base64.stringify(wordArray); 1831 | */ 1832 | stringify: function (wordArray) { 1833 | // Shortcuts 1834 | var words = wordArray.words; 1835 | var sigBytes = wordArray.sigBytes; 1836 | var map = this._map; 1837 | 1838 | // Clamp excess bits 1839 | wordArray.clamp(); 1840 | 1841 | // Convert 1842 | var base64Chars = []; 1843 | for (var i = 0; i < sigBytes; i += 3) { 1844 | var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 1845 | var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; 1846 | var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; 1847 | 1848 | var triplet = (byte1 << 16) | (byte2 << 8) | byte3; 1849 | 1850 | for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { 1851 | base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); 1852 | } 1853 | } 1854 | 1855 | // Add padding 1856 | var paddingChar = map.charAt(64); 1857 | if (paddingChar) { 1858 | while (base64Chars.length % 4) { 1859 | base64Chars.push(paddingChar); 1860 | } 1861 | } 1862 | 1863 | return base64Chars.join(''); 1864 | }, 1865 | 1866 | /** 1867 | * Converts a Base64 string to a word array. 1868 | * 1869 | * @param {string} base64Str The Base64 string. 1870 | * 1871 | * @return {WordArray} The word array. 1872 | * 1873 | * @static 1874 | * 1875 | * @example 1876 | * 1877 | * var wordArray = CryptoJS.enc.Base64.parse(base64String); 1878 | */ 1879 | parse: function (base64Str) { 1880 | // Shortcuts 1881 | var base64StrLength = base64Str.length; 1882 | var map = this._map; 1883 | 1884 | // Ignore padding 1885 | var paddingChar = map.charAt(64); 1886 | if (paddingChar) { 1887 | var paddingIndex = base64Str.indexOf(paddingChar); 1888 | if (paddingIndex != -1) { 1889 | base64StrLength = paddingIndex; 1890 | } 1891 | } 1892 | 1893 | // Convert 1894 | var words = []; 1895 | var nBytes = 0; 1896 | for (var i = 0; i < base64StrLength; i++) { 1897 | if (i % 4) { 1898 | var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); 1899 | var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); 1900 | words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); 1901 | nBytes++; 1902 | } 1903 | } 1904 | 1905 | return WordArray.create(words, nBytes); 1906 | }, 1907 | 1908 | _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 1909 | }; 1910 | }()); 1911 | 1912 | /* 1913 | CryptoJS v3.1.2 1914 | code.google.com/p/crypto-js 1915 | (c) 2009-2013 by Jeff Mott. All rights reserved. 1916 | code.google.com/p/crypto-js/wiki/License 1917 | */ 1918 | (function () { 1919 | // Shortcuts 1920 | var C = CryptoJS; 1921 | var C_lib = C.lib; 1922 | var Base = C_lib.Base; 1923 | var C_enc = C.enc; 1924 | var Utf8 = C_enc.Utf8; 1925 | var C_algo = C.algo; 1926 | 1927 | /** 1928 | * HMAC algorithm. 1929 | */ 1930 | var HMAC = C_algo.HMAC = Base.extend({ 1931 | /** 1932 | * Initializes a newly created HMAC. 1933 | * 1934 | * @param {Hasher} hasher The hash algorithm to use. 1935 | * @param {WordArray|string} key The secret key. 1936 | * 1937 | * @example 1938 | * 1939 | * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); 1940 | */ 1941 | init: function (hasher, key) { 1942 | // Init hasher 1943 | hasher = this._hasher = new hasher.init(); 1944 | 1945 | // Convert string to WordArray, else assume WordArray already 1946 | if (typeof key == 'string') { 1947 | key = Utf8.parse(key); 1948 | } 1949 | 1950 | // Shortcuts 1951 | var hasherBlockSize = hasher.blockSize; 1952 | var hasherBlockSizeBytes = hasherBlockSize * 4; 1953 | 1954 | // Allow arbitrary length keys 1955 | if (key.sigBytes > hasherBlockSizeBytes) { 1956 | key = hasher.finalize(key); 1957 | } 1958 | 1959 | // Clamp excess bits 1960 | key.clamp(); 1961 | 1962 | // Clone key for inner and outer pads 1963 | var oKey = this._oKey = key.clone(); 1964 | var iKey = this._iKey = key.clone(); 1965 | 1966 | // Shortcuts 1967 | var oKeyWords = oKey.words; 1968 | var iKeyWords = iKey.words; 1969 | 1970 | // XOR keys with pad constants 1971 | for (var i = 0; i < hasherBlockSize; i++) { 1972 | oKeyWords[i] ^= 0x5c5c5c5c; 1973 | iKeyWords[i] ^= 0x36363636; 1974 | } 1975 | oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; 1976 | 1977 | // Set initial values 1978 | this.reset(); 1979 | }, 1980 | 1981 | /** 1982 | * Resets this HMAC to its initial state. 1983 | * 1984 | * @example 1985 | * 1986 | * hmacHasher.reset(); 1987 | */ 1988 | reset: function () { 1989 | // Shortcut 1990 | var hasher = this._hasher; 1991 | 1992 | // Reset 1993 | hasher.reset(); 1994 | hasher.update(this._iKey); 1995 | }, 1996 | 1997 | /** 1998 | * Updates this HMAC with a message. 1999 | * 2000 | * @param {WordArray|string} messageUpdate The message to append. 2001 | * 2002 | * @return {HMAC} This HMAC instance. 2003 | * 2004 | * @example 2005 | * 2006 | * hmacHasher.update('message'); 2007 | * hmacHasher.update(wordArray); 2008 | */ 2009 | update: function (messageUpdate) { 2010 | this._hasher.update(messageUpdate); 2011 | 2012 | // Chainable 2013 | return this; 2014 | }, 2015 | 2016 | /** 2017 | * Finalizes the HMAC computation. 2018 | * Note that the finalize operation is effectively a destructive, read-once operation. 2019 | * 2020 | * @param {WordArray|string} messageUpdate (Optional) A final message update. 2021 | * 2022 | * @return {WordArray} The HMAC. 2023 | * 2024 | * @example 2025 | * 2026 | * var hmac = hmacHasher.finalize(); 2027 | * var hmac = hmacHasher.finalize('message'); 2028 | * var hmac = hmacHasher.finalize(wordArray); 2029 | */ 2030 | finalize: function (messageUpdate) { 2031 | // Shortcut 2032 | var hasher = this._hasher; 2033 | 2034 | // Compute HMAC 2035 | var innerHash = hasher.finalize(messageUpdate); 2036 | hasher.reset(); 2037 | var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); 2038 | 2039 | return hmac; 2040 | } 2041 | }); 2042 | }()); 2043 | 2044 | /* 2045 | CryptoJS v3.1.2 2046 | code.google.com/p/crypto-js 2047 | (c) 2009-2013 by Jeff Mott. All rights reserved. 2048 | code.google.com/p/crypto-js/wiki/License 2049 | */ 2050 | (function (Math) { 2051 | // Shortcuts 2052 | var C = CryptoJS; 2053 | var C_lib = C.lib; 2054 | var WordArray = C_lib.WordArray; 2055 | var Hasher = C_lib.Hasher; 2056 | var C_algo = C.algo; 2057 | 2058 | // Initialization and round constants tables 2059 | var H = []; 2060 | var K = []; 2061 | 2062 | // Compute constants 2063 | (function () { 2064 | function isPrime(n) { 2065 | var sqrtN = Math.sqrt(n); 2066 | for (var factor = 2; factor <= sqrtN; factor++) { 2067 | if (!(n % factor)) { 2068 | return false; 2069 | } 2070 | } 2071 | 2072 | return true; 2073 | } 2074 | 2075 | function getFractionalBits(n) { 2076 | return ((n - (n | 0)) * 0x100000000) | 0; 2077 | } 2078 | 2079 | var n = 2; 2080 | var nPrime = 0; 2081 | while (nPrime < 64) { 2082 | if (isPrime(n)) { 2083 | if (nPrime < 8) { 2084 | H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); 2085 | } 2086 | K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); 2087 | 2088 | nPrime++; 2089 | } 2090 | 2091 | n++; 2092 | } 2093 | }()); 2094 | 2095 | // Reusable object 2096 | var W = []; 2097 | 2098 | /** 2099 | * SHA-256 hash algorithm. 2100 | */ 2101 | var SHA256 = C_algo.SHA256 = Hasher.extend({ 2102 | _doReset: function () { 2103 | this._hash = new WordArray.init(H.slice(0)); 2104 | }, 2105 | 2106 | _doProcessBlock: function (M, offset) { 2107 | // Shortcut 2108 | var H = this._hash.words; 2109 | 2110 | // Working variables 2111 | var a = H[0]; 2112 | var b = H[1]; 2113 | var c = H[2]; 2114 | var d = H[3]; 2115 | var e = H[4]; 2116 | var f = H[5]; 2117 | var g = H[6]; 2118 | var h = H[7]; 2119 | 2120 | // Computation 2121 | for (var i = 0; i < 64; i++) { 2122 | if (i < 16) { 2123 | W[i] = M[offset + i] | 0; 2124 | } else { 2125 | var gamma0x = W[i - 15]; 2126 | var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ 2127 | ((gamma0x << 14) | (gamma0x >>> 18)) ^ 2128 | (gamma0x >>> 3); 2129 | 2130 | var gamma1x = W[i - 2]; 2131 | var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ 2132 | ((gamma1x << 13) | (gamma1x >>> 19)) ^ 2133 | (gamma1x >>> 10); 2134 | 2135 | W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; 2136 | } 2137 | 2138 | var ch = (e & f) ^ (~e & g); 2139 | var maj = (a & b) ^ (a & c) ^ (b & c); 2140 | 2141 | var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); 2142 | var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); 2143 | 2144 | var t1 = h + sigma1 + ch + K[i] + W[i]; 2145 | var t2 = sigma0 + maj; 2146 | 2147 | h = g; 2148 | g = f; 2149 | f = e; 2150 | e = (d + t1) | 0; 2151 | d = c; 2152 | c = b; 2153 | b = a; 2154 | a = (t1 + t2) | 0; 2155 | } 2156 | 2157 | // Intermediate hash value 2158 | H[0] = (H[0] + a) | 0; 2159 | H[1] = (H[1] + b) | 0; 2160 | H[2] = (H[2] + c) | 0; 2161 | H[3] = (H[3] + d) | 0; 2162 | H[4] = (H[4] + e) | 0; 2163 | H[5] = (H[5] + f) | 0; 2164 | H[6] = (H[6] + g) | 0; 2165 | H[7] = (H[7] + h) | 0; 2166 | }, 2167 | 2168 | _doFinalize: function () { 2169 | // Shortcuts 2170 | var data = this._data; 2171 | var dataWords = data.words; 2172 | 2173 | var nBitsTotal = this._nDataBytes * 8; 2174 | var nBitsLeft = data.sigBytes * 8; 2175 | 2176 | // Add padding 2177 | dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); 2178 | dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); 2179 | dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; 2180 | data.sigBytes = dataWords.length * 4; 2181 | 2182 | // Hash final blocks 2183 | this._process(); 2184 | 2185 | // Return final computed hash 2186 | return this._hash; 2187 | }, 2188 | 2189 | clone: function () { 2190 | var clone = Hasher.clone.call(this); 2191 | clone._hash = this._hash.clone(); 2192 | 2193 | return clone; 2194 | } 2195 | }); 2196 | 2197 | /** 2198 | * Shortcut function to the hasher's object interface. 2199 | * 2200 | * @param {WordArray|string} message The message to hash. 2201 | * 2202 | * @return {WordArray} The hash. 2203 | * 2204 | * @static 2205 | * 2206 | * @example 2207 | * 2208 | * var hash = CryptoJS.SHA256('message'); 2209 | * var hash = CryptoJS.SHA256(wordArray); 2210 | */ 2211 | C.SHA256 = Hasher._createHelper(SHA256); 2212 | 2213 | /** 2214 | * Shortcut function to the HMAC's object interface. 2215 | * 2216 | * @param {WordArray|string} message The message to hash. 2217 | * @param {WordArray|string} key The secret key. 2218 | * 2219 | * @return {WordArray} The HMAC. 2220 | * 2221 | * @static 2222 | * 2223 | * @example 2224 | * 2225 | * var hmac = CryptoJS.HmacSHA256(message, key); 2226 | */ 2227 | C.HmacSHA256 = Hasher._createHmacHelper(SHA256); 2228 | }(Math)); 2229 | 2230 | /* 2231 | CryptoJS v3.1.2 2232 | code.google.com/p/crypto-js 2233 | (c) 2009-2013 by Jeff Mott. All rights reserved. 2234 | code.google.com/p/crypto-js/wiki/License 2235 | */ 2236 | (function () { 2237 | // Shortcuts 2238 | var C = CryptoJS; 2239 | var C_lib = C.lib; 2240 | var Base = C_lib.Base; 2241 | var WordArray = C_lib.WordArray; 2242 | var C_algo = C.algo; 2243 | var SHA1 = C_algo.SHA1; 2244 | var HMAC = C_algo.HMAC; 2245 | 2246 | /** 2247 | * Password-Based Key Derivation Function 2 algorithm. 2248 | */ 2249 | var PBKDF2 = C_algo.PBKDF2 = Base.extend({ 2250 | /** 2251 | * Configuration options. 2252 | * 2253 | * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) 2254 | * @property {Hasher} hasher The hasher to use. Default: SHA1 2255 | * @property {number} iterations The number of iterations to perform. Default: 1 2256 | */ 2257 | cfg: Base.extend({ 2258 | keySize: 128/32, 2259 | hasher: SHA1, 2260 | iterations: 1 2261 | }), 2262 | 2263 | /** 2264 | * Initializes a newly created key derivation function. 2265 | * 2266 | * @param {Object} cfg (Optional) The configuration options to use for the derivation. 2267 | * 2268 | * @example 2269 | * 2270 | * var kdf = CryptoJS.algo.PBKDF2.create(); 2271 | * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); 2272 | * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); 2273 | */ 2274 | init: function (cfg) { 2275 | this.cfg = this.cfg.extend(cfg); 2276 | }, 2277 | 2278 | /** 2279 | * Computes the Password-Based Key Derivation Function 2. 2280 | * 2281 | * @param {WordArray|string} password The password. 2282 | * @param {WordArray|string} salt A salt. 2283 | * 2284 | * @return {WordArray} The derived key. 2285 | * 2286 | * @example 2287 | * 2288 | * var key = kdf.compute(password, salt); 2289 | */ 2290 | compute: function (password, salt) { 2291 | // Shortcut 2292 | var cfg = this.cfg; 2293 | 2294 | // Init HMAC 2295 | var hmac = HMAC.create(cfg.hasher, password); 2296 | 2297 | // Initial values 2298 | var derivedKey = WordArray.create(); 2299 | var blockIndex = WordArray.create([0x00000001]); 2300 | 2301 | // Shortcuts 2302 | var derivedKeyWords = derivedKey.words; 2303 | var blockIndexWords = blockIndex.words; 2304 | var keySize = cfg.keySize; 2305 | var iterations = cfg.iterations; 2306 | 2307 | // Generate key 2308 | while (derivedKeyWords.length < keySize) { 2309 | var block = hmac.update(salt).finalize(blockIndex); 2310 | hmac.reset(); 2311 | 2312 | // Shortcuts 2313 | var blockWords = block.words; 2314 | var blockWordsLength = blockWords.length; 2315 | 2316 | // Iterations 2317 | var intermediate = block; 2318 | for (var i = 1; i < iterations; i++) { 2319 | intermediate = hmac.finalize(intermediate); 2320 | hmac.reset(); 2321 | 2322 | // Shortcut 2323 | var intermediateWords = intermediate.words; 2324 | 2325 | // XOR intermediate with block 2326 | for (var j = 0; j < blockWordsLength; j++) { 2327 | blockWords[j] ^= intermediateWords[j]; 2328 | } 2329 | } 2330 | 2331 | derivedKey.concat(block); 2332 | blockIndexWords[0]++; 2333 | } 2334 | derivedKey.sigBytes = keySize * 4; 2335 | 2336 | return derivedKey; 2337 | } 2338 | }); 2339 | 2340 | /** 2341 | * Computes the Password-Based Key Derivation Function 2. 2342 | * 2343 | * @param {WordArray|string} password The password. 2344 | * @param {WordArray|string} salt A salt. 2345 | * @param {Object} cfg (Optional) The configuration options to use for this computation. 2346 | * 2347 | * @return {WordArray} The derived key. 2348 | * 2349 | * @static 2350 | * 2351 | * @example 2352 | * 2353 | * var key = CryptoJS.PBKDF2(password, salt); 2354 | * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); 2355 | * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); 2356 | */ 2357 | C.PBKDF2 = function (password, salt, cfg) { 2358 | return PBKDF2.create(cfg).compute(password, salt); 2359 | }; 2360 | }()); 2361 | 2362 | /** @license MIT License (c) 2011-2013 Copyright Tavendo GmbH. */ 2363 | 2364 | /** 2365 | * AutobahnJS - http://autobahn.ws 2366 | * 2367 | * A lightweight implementation of 2368 | * 2369 | * WAMP (The WebSocket Application Messaging Protocol) - http://wamp.ws 2370 | * 2371 | * Provides asynchronous RPC/PubSub over WebSocket. 2372 | * 2373 | * Copyright 2011-2013 Tavendo GmbH. Licensed under the MIT License. 2374 | * See license text at http://www.opensource.org/licenses/mit-license.php 2375 | */ 2376 | 2377 | /*global console: false, MozWebSocket: false, when: false, CryptoJS: false */ 2378 | 2379 | /** @define {string} */ 2380 | var AUTOBAHNJS_VERSION = '?.?.?'; 2381 | 2382 | 2383 | (function (root, factory) { 2384 | if (typeof define === 'function' && define.amd) { 2385 | // AMD. Register as an anonymous module. 2386 | define(['when'], function (when) { 2387 | // Also create a global in case some scripts 2388 | // that are loaded still are looking for 2389 | // a global even when an AMD loader is in use. 2390 | return (root.ab = factory(when)); 2391 | }); 2392 | 2393 | } else if (typeof exports !== 'undefined') { 2394 | // Support Node.js specific `module.exports` (which can be a function) 2395 | if (typeof module != 'undefined' && module.exports) { 2396 | exports = module.exports = factory(root.when); 2397 | } 2398 | // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) 2399 | //exports.ab = exports; 2400 | 2401 | } else { 2402 | // Browser globals 2403 | root.ab = factory(root.when); 2404 | } 2405 | } (this, function (when) { 2406 | 2407 | "use strict"; 2408 | 2409 | var ab = {}; 2410 | ab._version = AUTOBAHNJS_VERSION; 2411 | 2412 | /** 2413 | * Fallbacks for browsers lacking 2414 | * 2415 | * Array.prototype.indexOf 2416 | * Array.prototype.forEach 2417 | * 2418 | * most notably MSIE8. 2419 | * 2420 | * Source: 2421 | * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf 2422 | * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach 2423 | */ 2424 | (function () { 2425 | if (!Array.prototype.indexOf) { 2426 | Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { 2427 | "use strict"; 2428 | if (this === null) { 2429 | throw new TypeError(); 2430 | } 2431 | var t = new Object(this); 2432 | var len = t.length >>> 0; 2433 | if (len === 0) { 2434 | return -1; 2435 | } 2436 | var n = 0; 2437 | if (arguments.length > 0) { 2438 | n = Number(arguments[1]); 2439 | if (n !== n) { // shortcut for verifying if it's NaN 2440 | n = 0; 2441 | } else if (n !== 0 && n !== Infinity && n !== -Infinity) { 2442 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); 2443 | } 2444 | } 2445 | if (n >= len) { 2446 | return -1; 2447 | } 2448 | var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 2449 | for (; k < len; k++) { 2450 | if (k in t && t[k] === searchElement) { 2451 | return k; 2452 | } 2453 | } 2454 | return -1; 2455 | }; 2456 | } 2457 | 2458 | if (!Array.prototype.forEach) { 2459 | 2460 | Array.prototype.forEach = function (callback, thisArg) { 2461 | 2462 | var T, k; 2463 | 2464 | if (this === null) { 2465 | throw new TypeError(" this is null or not defined"); 2466 | } 2467 | 2468 | // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 2469 | var O = new Object(this); 2470 | 2471 | // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 2472 | // 3. Let len be ToUint32(lenValue). 2473 | var len = O.length >>> 0; // Hack to convert O.length to a UInt32 2474 | 2475 | // 4. If IsCallable(callback) is false, throw a TypeError exception. 2476 | // See: http://es5.github.com/#x9.11 2477 | if ({}.toString.call(callback) !== "[object Function]") { 2478 | throw new TypeError(callback + " is not a function"); 2479 | } 2480 | 2481 | // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 2482 | if (thisArg) { 2483 | T = thisArg; 2484 | } 2485 | 2486 | // 6. Let k be 0 2487 | k = 0; 2488 | 2489 | // 7. Repeat, while k < len 2490 | while (k < len) { 2491 | 2492 | var kValue; 2493 | 2494 | // a. Let Pk be ToString(k). 2495 | // This is implicit for LHS operands of the in operator 2496 | // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 2497 | // This step can be combined with c 2498 | // c. If kPresent is true, then 2499 | if (k in O) { 2500 | 2501 | // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 2502 | kValue = O[k]; 2503 | 2504 | // ii. Call the Call internal method of callback with T as the this value and 2505 | // argument list containing kValue, k, and O. 2506 | callback.call(T, kValue, k, O); 2507 | } 2508 | // d. Increase k by 1. 2509 | k++; 2510 | } 2511 | // 8. return undefined 2512 | }; 2513 | } 2514 | 2515 | })(); 2516 | 2517 | 2518 | // Helper to slice out browser / version from userAgent 2519 | ab._sliceUserAgent = function (str, delim, delim2) { 2520 | var ver = []; 2521 | var ua = navigator.userAgent; 2522 | var i = ua.indexOf(str); 2523 | var j = ua.indexOf(delim, i); 2524 | if (j < 0) { 2525 | j = ua.length; 2526 | } 2527 | var agent = ua.slice(i, j).split(delim2); 2528 | var v = agent[1].split('.'); 2529 | for (var k = 0; k < v.length; ++k) { 2530 | ver.push(parseInt(v[k], 10)); 2531 | } 2532 | return {name: agent[0], version: ver}; 2533 | }; 2534 | 2535 | /** 2536 | * Detect browser and browser version. 2537 | */ 2538 | ab.getBrowser = function () { 2539 | 2540 | var ua = navigator.userAgent; 2541 | if (ua.indexOf("Chrome") > -1) { 2542 | return ab._sliceUserAgent("Chrome", " ", "/"); 2543 | } else if (ua.indexOf("Safari") > -1) { 2544 | return ab._sliceUserAgent("Safari", " ", "/"); 2545 | } else if (ua.indexOf("Firefox") > -1) { 2546 | return ab._sliceUserAgent("Firefox", " ", "/"); 2547 | } else if (ua.indexOf("MSIE") > -1) { 2548 | return ab._sliceUserAgent("MSIE", ";", " "); 2549 | } else { 2550 | return null; 2551 | } 2552 | }; 2553 | 2554 | 2555 | ab.getServerUrl = function (wsPath, fallbackUrl) { 2556 | if (window.location.protocol === "file:") { 2557 | if (fallbackUrl) { 2558 | return fallbackUrl; 2559 | } else { 2560 | return "ws://127.0.0.1/ws"; 2561 | } 2562 | } else { 2563 | var scheme = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; 2564 | var port = window.location.port !== "" ? ':' + window.location.port : ''; 2565 | var path = wsPath ? wsPath : 'ws'; 2566 | return scheme + window.location.hostname + port + "/" + path; 2567 | } 2568 | }; 2569 | 2570 | 2571 | // Logging message for unsupported browser. 2572 | ab.browserNotSupportedMessage = "Browser does not support WebSockets (RFC6455)"; 2573 | 2574 | 2575 | // PBKDF2-base key derivation function for salted WAMP-CRA 2576 | ab.deriveKey = function (secret, extra) { 2577 | if (extra && extra.salt) { 2578 | var salt = extra.salt; 2579 | var keylen = extra.keylen || 32; 2580 | var iterations = extra.iterations || 10000; 2581 | var key = CryptoJS.PBKDF2(secret, salt, { keySize: keylen / 4, iterations: iterations, hasher: CryptoJS.algo.SHA256 }); 2582 | return key.toString(CryptoJS.enc.Base64); 2583 | } else { 2584 | return secret; 2585 | } 2586 | }; 2587 | 2588 | 2589 | ab._idchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 2590 | ab._idlen = 16; 2591 | ab._subprotocol = "wamp"; 2592 | 2593 | ab._newid = function () { 2594 | var id = ""; 2595 | for (var i = 0; i < ab._idlen; i += 1) { 2596 | id += ab._idchars.charAt(Math.floor(Math.random() * ab._idchars.length)); 2597 | } 2598 | return id; 2599 | }; 2600 | 2601 | ab._newidFast = function () { 2602 | return Math.random().toString(36); 2603 | }; 2604 | 2605 | ab.log = function () { 2606 | //console.log.apply(console, !!arguments.length ? arguments : [this]); 2607 | if (arguments.length > 1) { 2608 | console.group("Log Item"); 2609 | for (var i = 0; i < arguments.length; i += 1) { 2610 | console.log(arguments[i]); 2611 | } 2612 | console.groupEnd(); 2613 | } else { 2614 | console.log(arguments[0]); 2615 | } 2616 | }; 2617 | 2618 | ab._debugrpc = false; 2619 | ab._debugpubsub = false; 2620 | ab._debugws = false; 2621 | ab._debugconnect = false; 2622 | 2623 | ab.debug = function (debugWamp, debugWs, debugConnect) { 2624 | if ("console" in window) { 2625 | ab._debugrpc = debugWamp; 2626 | ab._debugpubsub = debugWamp; 2627 | ab._debugws = debugWs; 2628 | ab._debugconnect = debugConnect; 2629 | } else { 2630 | throw "browser does not support console object"; 2631 | } 2632 | }; 2633 | 2634 | ab.version = function () { 2635 | return ab._version; 2636 | }; 2637 | 2638 | ab.PrefixMap = function () { 2639 | 2640 | var self = this; 2641 | self._index = {}; 2642 | self._rindex = {}; 2643 | }; 2644 | 2645 | ab.PrefixMap.prototype.get = function (prefix) { 2646 | 2647 | var self = this; 2648 | return self._index[prefix]; 2649 | }; 2650 | 2651 | ab.PrefixMap.prototype.set = function (prefix, uri) { 2652 | 2653 | var self = this; 2654 | self._index[prefix] = uri; 2655 | self._rindex[uri] = prefix; 2656 | }; 2657 | 2658 | ab.PrefixMap.prototype.setDefault = function (uri) { 2659 | 2660 | var self = this; 2661 | self._index[""] = uri; 2662 | self._rindex[uri] = ""; 2663 | }; 2664 | 2665 | ab.PrefixMap.prototype.remove = function (prefix) { 2666 | 2667 | var self = this; 2668 | var uri = self._index[prefix]; 2669 | if (uri) { 2670 | delete self._index[prefix]; 2671 | delete self._rindex[uri]; 2672 | } 2673 | }; 2674 | 2675 | ab.PrefixMap.prototype.resolve = function (curie, pass) { 2676 | 2677 | var self = this; 2678 | 2679 | // skip if not a CURIE 2680 | var i = curie.indexOf(":"); 2681 | if (i >= 0) { 2682 | var prefix = curie.substring(0, i); 2683 | if (self._index[prefix]) { 2684 | return self._index[prefix] + curie.substring(i + 1); 2685 | } 2686 | } 2687 | 2688 | // either pass-through or null 2689 | if (pass === true) { 2690 | return curie; 2691 | } else { 2692 | return null; 2693 | } 2694 | }; 2695 | 2696 | ab.PrefixMap.prototype.shrink = function (uri, pass) { 2697 | 2698 | var self = this; 2699 | 2700 | for (var i = uri.length; i > 0; i -= 1) { 2701 | var u = uri.substring(0, i); 2702 | var p = self._rindex[u]; 2703 | if (p) { 2704 | return p + ":" + uri.substring(i); 2705 | } 2706 | } 2707 | 2708 | // either pass-through or null 2709 | if (pass === true) { 2710 | return uri; 2711 | } else { 2712 | return null; 2713 | } 2714 | }; 2715 | 2716 | 2717 | ab._MESSAGE_TYPEID_WELCOME = 0; 2718 | ab._MESSAGE_TYPEID_PREFIX = 1; 2719 | ab._MESSAGE_TYPEID_CALL = 2; 2720 | ab._MESSAGE_TYPEID_CALL_RESULT = 3; 2721 | ab._MESSAGE_TYPEID_CALL_ERROR = 4; 2722 | ab._MESSAGE_TYPEID_SUBSCRIBE = 5; 2723 | ab._MESSAGE_TYPEID_UNSUBSCRIBE = 6; 2724 | ab._MESSAGE_TYPEID_PUBLISH = 7; 2725 | ab._MESSAGE_TYPEID_EVENT = 8; 2726 | 2727 | ab.CONNECTION_CLOSED = 0; 2728 | ab.CONNECTION_LOST = 1; 2729 | ab.CONNECTION_RETRIES_EXCEEDED = 2; 2730 | ab.CONNECTION_UNREACHABLE = 3; 2731 | ab.CONNECTION_UNSUPPORTED = 4; 2732 | ab.CONNECTION_UNREACHABLE_SCHEDULED_RECONNECT = 5; 2733 | ab.CONNECTION_LOST_SCHEDULED_RECONNECT = 6; 2734 | 2735 | // ab.Deferred = when.defer; 2736 | ab.Deferred = jQuery.Deferred; 2737 | 2738 | ab._construct = function (url, protocols) { 2739 | if ("WebSocket" in window) { 2740 | // Chrome, MSIE, newer Firefox 2741 | if (protocols) { 2742 | return new WebSocket(url, protocols); 2743 | } else { 2744 | return new WebSocket(url); 2745 | } 2746 | } else if ("MozWebSocket" in window) { 2747 | // older versions of Firefox prefix the WebSocket object 2748 | if (protocols) { 2749 | return new MozWebSocket(url, protocols); 2750 | } else { 2751 | return new MozWebSocket(url); 2752 | } 2753 | } else { 2754 | return null; 2755 | } 2756 | }; 2757 | 2758 | ab.Session = function (wsuri, onopen, onclose, options) { 2759 | 2760 | var self = this; 2761 | 2762 | self._wsuri = wsuri; 2763 | self._options = options; 2764 | self._websocket_onopen = onopen; 2765 | self._websocket_onclose = onclose; 2766 | 2767 | self._websocket = null; 2768 | self._websocket_connected = false; 2769 | 2770 | self._session_id = null; 2771 | self._wamp_version = null; 2772 | self._server = null; 2773 | 2774 | self._calls = {}; 2775 | self._subscriptions = {}; 2776 | self._prefixes = new ab.PrefixMap(); 2777 | 2778 | self._txcnt = 0; 2779 | self._rxcnt = 0; 2780 | 2781 | if (self._options && self._options.skipSubprotocolAnnounce) { 2782 | self._websocket = ab._construct(self._wsuri); 2783 | } else { 2784 | self._websocket = ab._construct(self._wsuri, [ab._subprotocol]); 2785 | } 2786 | 2787 | if (!self._websocket) { 2788 | if (onclose !== undefined) { 2789 | onclose(ab.CONNECTION_UNSUPPORTED); 2790 | return; 2791 | } else { 2792 | throw ab.browserNotSupportedMessage; 2793 | } 2794 | } 2795 | 2796 | self._websocket.onmessage = function (e) 2797 | { 2798 | if (ab._debugws) { 2799 | self._rxcnt += 1; 2800 | console.group("WS Receive"); 2801 | console.info(self._wsuri + " [" + self._session_id + "]"); 2802 | console.log(self._rxcnt); 2803 | console.log(e.data); 2804 | console.groupEnd(); 2805 | } 2806 | 2807 | var o = JSON.parse(e.data); 2808 | if (o[1] in self._calls) 2809 | { 2810 | if (o[0] === ab._MESSAGE_TYPEID_CALL_RESULT) { 2811 | 2812 | var dr = self._calls[o[1]]; 2813 | var r = o[2]; 2814 | 2815 | if (ab._debugrpc && dr._ab_callobj !== undefined) { 2816 | console.group("WAMP Call", dr._ab_callobj[2]); 2817 | console.timeEnd(dr._ab_tid); 2818 | console.group("Arguments"); 2819 | for (var i = 3; i < dr._ab_callobj.length; i += 1) { 2820 | var arg = dr._ab_callobj[i]; 2821 | if (arg !== undefined) { 2822 | console.log(arg); 2823 | } else { 2824 | break; 2825 | } 2826 | } 2827 | console.groupEnd(); 2828 | console.group("Result"); 2829 | console.log(r); 2830 | console.groupEnd(); 2831 | console.groupEnd(); 2832 | } 2833 | 2834 | dr.resolve(r); 2835 | } 2836 | else if (o[0] === ab._MESSAGE_TYPEID_CALL_ERROR) { 2837 | 2838 | var de = self._calls[o[1]]; 2839 | var uri_ = o[2]; 2840 | var desc_ = o[3]; 2841 | var detail_ = o[4]; 2842 | 2843 | if (ab._debugrpc && de._ab_callobj !== undefined) { 2844 | console.group("WAMP Call", de._ab_callobj[2]); 2845 | console.timeEnd(de._ab_tid); 2846 | console.group("Arguments"); 2847 | for (var j = 3; j < de._ab_callobj.length; j += 1) { 2848 | var arg2 = de._ab_callobj[j]; 2849 | if (arg2 !== undefined) { 2850 | console.log(arg2); 2851 | } else { 2852 | break; 2853 | } 2854 | } 2855 | console.groupEnd(); 2856 | console.group("Error"); 2857 | console.log(uri_); 2858 | console.log(desc_); 2859 | if (detail_ !== undefined) { 2860 | console.log(detail_); 2861 | } 2862 | console.groupEnd(); 2863 | console.groupEnd(); 2864 | } 2865 | 2866 | if (detail_ !== undefined) { 2867 | de.reject({uri: uri_, desc: desc_, detail: detail_}); 2868 | } else { 2869 | de.reject({uri: uri_, desc: desc_}); 2870 | } 2871 | } 2872 | delete self._calls[o[1]]; 2873 | } 2874 | else if (o[0] === ab._MESSAGE_TYPEID_EVENT) 2875 | { 2876 | var subid = self._prefixes.resolve(o[1], true); 2877 | if (subid in self._subscriptions) { 2878 | 2879 | var uri2 = o[1]; 2880 | var val = o[2]; 2881 | 2882 | if (ab._debugpubsub) { 2883 | console.group("WAMP Event"); 2884 | console.info(self._wsuri + " [" + self._session_id + "]"); 2885 | console.log(uri2); 2886 | console.log(val); 2887 | console.groupEnd(); 2888 | } 2889 | 2890 | self._subscriptions[subid].forEach(function (callback) { 2891 | 2892 | callback(uri2, val); 2893 | }); 2894 | } 2895 | else { 2896 | // ignore unsolicited event! 2897 | } 2898 | } 2899 | else if (o[0] === ab._MESSAGE_TYPEID_WELCOME) 2900 | { 2901 | if (self._session_id === null) { 2902 | self._session_id = o[1]; 2903 | self._wamp_version = o[2]; 2904 | self._server = o[3]; 2905 | 2906 | if (ab._debugrpc || ab._debugpubsub) { 2907 | console.group("WAMP Welcome"); 2908 | console.info(self._wsuri + " [" + self._session_id + "]"); 2909 | console.log(self._wamp_version); 2910 | console.log(self._server); 2911 | console.groupEnd(); 2912 | } 2913 | 2914 | // only now that we have received the initial server-to-client 2915 | // welcome message, fire application onopen() hook 2916 | if (self._websocket_onopen !== null) { 2917 | self._websocket_onopen(); 2918 | } 2919 | } else { 2920 | throw "protocol error (welcome message received more than once)"; 2921 | } 2922 | } 2923 | }; 2924 | 2925 | self._websocket.onopen = function (e) 2926 | { 2927 | // check if we can speak WAMP! 2928 | if (self._websocket.protocol !== ab._subprotocol) { 2929 | 2930 | if (typeof self._websocket.protocol === 'undefined') { 2931 | // i.e. Safari does subprotocol negotiation (broken), but then 2932 | // does NOT set the protocol attribute of the websocket object (broken) 2933 | // 2934 | if (ab._debugws) { 2935 | console.group("WS Warning"); 2936 | console.info(self._wsuri); 2937 | console.log("WebSocket object has no protocol attribute: WAMP subprotocol check skipped!"); 2938 | console.groupEnd(); 2939 | } 2940 | } 2941 | else if (self._options && self._options.skipSubprotocolCheck) { 2942 | // WAMP subprotocol check disabled by session option 2943 | // 2944 | if (ab._debugws) { 2945 | console.group("WS Warning"); 2946 | console.info(self._wsuri); 2947 | console.log("Server does not speak WAMP, but subprotocol check disabled by option!"); 2948 | console.log(self._websocket.protocol); 2949 | console.groupEnd(); 2950 | } 2951 | } else { 2952 | // we only speak WAMP .. if the server denied us this, we bail out. 2953 | // 2954 | self._websocket.close(1000, "server does not speak WAMP"); 2955 | throw "server does not speak WAMP (but '" + self._websocket.protocol + "' !)"; 2956 | } 2957 | } 2958 | if (ab._debugws) { 2959 | console.group("WAMP Connect"); 2960 | console.info(self._wsuri); 2961 | console.log(self._websocket.protocol); 2962 | console.groupEnd(); 2963 | } 2964 | self._websocket_connected = true; 2965 | }; 2966 | 2967 | self._websocket.onerror = function (e) 2968 | { 2969 | // FF fires this upon unclean closes 2970 | // Chrome does not fire this 2971 | }; 2972 | 2973 | self._websocket.onclose = function (e) 2974 | { 2975 | if (ab._debugws) { 2976 | if (self._websocket_connected) { 2977 | console.log("Autobahn connection to " + self._wsuri + " lost (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ")."); 2978 | } else { 2979 | console.log("Autobahn could not connect to " + self._wsuri + " (code " + e.code + ", reason '" + e.reason + "', wasClean " + e.wasClean + ")."); 2980 | } 2981 | } 2982 | 2983 | // fire app callback 2984 | if (self._websocket_onclose !== undefined) { 2985 | if (self._websocket_connected) { 2986 | if (e.wasClean) { 2987 | // connection was closed cleanly (closing HS was performed) 2988 | self._websocket_onclose(ab.CONNECTION_CLOSED, "WS-" + e.code + ": " + e.reason); 2989 | } else { 2990 | // connection was closed uncleanly (lost without closing HS) 2991 | self._websocket_onclose(ab.CONNECTION_LOST); 2992 | } 2993 | } else { 2994 | // connection could not be established in the first place 2995 | self._websocket_onclose(ab.CONNECTION_UNREACHABLE); 2996 | } 2997 | } 2998 | 2999 | // cleanup - reconnect requires a new session object! 3000 | self._websocket_connected = false; 3001 | self._wsuri = null; 3002 | self._websocket_onopen = null; 3003 | self._websocket_onclose = null; 3004 | self._websocket = null; 3005 | }; 3006 | 3007 | self.log = function () { 3008 | if (self._options && 'sessionIdent' in self._options) { 3009 | console.group("WAMP Session '" + self._options.sessionIdent + "' [" + self._session_id + "]"); 3010 | } else { 3011 | console.group("WAMP Session " + "[" + self._session_id + "]"); 3012 | } 3013 | for (var i = 0; i < arguments.length; ++i) { 3014 | console.log(arguments[i]); 3015 | } 3016 | console.groupEnd(); 3017 | }; 3018 | }; 3019 | 3020 | 3021 | ab.Session.prototype._send = function (msg) { 3022 | 3023 | var self = this; 3024 | 3025 | if (!self._websocket_connected) { 3026 | throw "Autobahn not connected"; 3027 | } 3028 | 3029 | var rmsg; 3030 | switch (true) 3031 | { 3032 | // In the event that prototype library is in existance run the toJSON method prototype provides 3033 | // else run the standard JSON.stringify 3034 | // this is a very clever problem that causes json to be double-quote-encoded. 3035 | case window.Prototype && typeof top.window.__prototype_deleted === 'undefined': 3036 | case typeof msg.toJSON === 'function': 3037 | rmsg = msg.toJSON(); 3038 | break; 3039 | 3040 | // we could do instead 3041 | // msg.toJSON = function(){return msg}; 3042 | // rmsg = JSON.stringify(msg); 3043 | default: 3044 | rmsg = JSON.stringify(msg); 3045 | } 3046 | self._websocket.send(rmsg); 3047 | self._txcnt += 1; 3048 | 3049 | if (ab._debugws) { 3050 | console.group("WS Send"); 3051 | console.info(self._wsuri + " [" + self._session_id + "]"); 3052 | console.log(self._txcnt); 3053 | console.log(rmsg); 3054 | console.groupEnd(); 3055 | } 3056 | }; 3057 | 3058 | 3059 | ab.Session.prototype.close = function () { 3060 | 3061 | var self = this; 3062 | 3063 | if (self._websocket_connected) { 3064 | self._websocket.close(); 3065 | } else { 3066 | //throw "Autobahn not connected"; 3067 | } 3068 | }; 3069 | 3070 | 3071 | ab.Session.prototype.sessionid = function () { 3072 | 3073 | var self = this; 3074 | return self._session_id; 3075 | }; 3076 | 3077 | 3078 | ab.Session.prototype.wsuri = function () { 3079 | 3080 | var self = this; 3081 | return self._wsuri; 3082 | }; 3083 | 3084 | 3085 | ab.Session.prototype.shrink = function (uri, pass) { 3086 | 3087 | var self = this; 3088 | if (pass === undefined) pass = true; 3089 | return self._prefixes.shrink(uri, pass); 3090 | }; 3091 | 3092 | 3093 | ab.Session.prototype.resolve = function (curie, pass) { 3094 | 3095 | var self = this; 3096 | if (pass === undefined) pass = true; 3097 | return self._prefixes.resolve(curie, pass); 3098 | }; 3099 | 3100 | 3101 | ab.Session.prototype.prefix = function (prefix, uri) { 3102 | 3103 | var self = this; 3104 | 3105 | /* 3106 | if (self._prefixes.get(prefix) !== undefined) { 3107 | throw "prefix '" + prefix + "' already defined"; 3108 | } 3109 | */ 3110 | 3111 | self._prefixes.set(prefix, uri); 3112 | 3113 | if (ab._debugrpc || ab._debugpubsub) { 3114 | console.group("WAMP Prefix"); 3115 | console.info(self._wsuri + " [" + self._session_id + "]"); 3116 | console.log(prefix); 3117 | console.log(uri); 3118 | console.groupEnd(); 3119 | } 3120 | 3121 | var msg = [ab._MESSAGE_TYPEID_PREFIX, prefix, uri]; 3122 | self._send(msg); 3123 | }; 3124 | 3125 | 3126 | ab.Session.prototype.call = function () { 3127 | 3128 | var self = this; 3129 | 3130 | var d = new ab.Deferred(); 3131 | var callid; 3132 | while (true) { 3133 | callid = ab._newidFast(); 3134 | if (!(callid in self._calls)) { 3135 | break; 3136 | } 3137 | } 3138 | self._calls[callid] = d; 3139 | 3140 | var procuri = self._prefixes.shrink(arguments[0], true); 3141 | var obj = [ab._MESSAGE_TYPEID_CALL, callid, procuri]; 3142 | for (var i = 1; i < arguments.length; i += 1) { 3143 | obj.push(arguments[i]); 3144 | } 3145 | 3146 | self._send(obj); 3147 | 3148 | if (ab._debugrpc) { 3149 | d._ab_callobj = obj; 3150 | d._ab_tid = self._wsuri + " [" + self._session_id + "][" + callid + "]"; 3151 | console.time(d._ab_tid); 3152 | console.info(); 3153 | } 3154 | 3155 | if (d.promise.then) { 3156 | // whenjs has the actual user promise in an attribute 3157 | return d.promise; 3158 | } else { 3159 | return d; 3160 | } 3161 | }; 3162 | 3163 | 3164 | ab.Session.prototype.subscribe = function (topicuri, callback) { 3165 | 3166 | var self = this; 3167 | 3168 | // subscribe by sending WAMP message when topic not already subscribed 3169 | // 3170 | var rtopicuri = self._prefixes.resolve(topicuri, true); 3171 | if (!(rtopicuri in self._subscriptions)) { 3172 | 3173 | if (ab._debugpubsub) { 3174 | console.group("WAMP Subscribe"); 3175 | console.info(self._wsuri + " [" + self._session_id + "]"); 3176 | console.log(topicuri); 3177 | console.log(callback); 3178 | console.groupEnd(); 3179 | } 3180 | 3181 | var msg = [ab._MESSAGE_TYPEID_SUBSCRIBE, topicuri]; 3182 | self._send(msg); 3183 | 3184 | self._subscriptions[rtopicuri] = []; 3185 | } 3186 | 3187 | // add callback to event listeners list if not already in list 3188 | // 3189 | var i = self._subscriptions[rtopicuri].indexOf(callback); 3190 | if (i === -1) { 3191 | self._subscriptions[rtopicuri].push(callback); 3192 | } 3193 | else { 3194 | throw "callback " + callback + " already subscribed for topic " + rtopicuri; 3195 | } 3196 | }; 3197 | 3198 | 3199 | ab.Session.prototype.unsubscribe = function (topicuri, callback) { 3200 | 3201 | var self = this; 3202 | 3203 | var rtopicuri = self._prefixes.resolve(topicuri, true); 3204 | if (!(rtopicuri in self._subscriptions)) { 3205 | throw "not subscribed to topic " + rtopicuri; 3206 | } 3207 | else { 3208 | var removed; 3209 | if (callback !== undefined) { 3210 | var idx = self._subscriptions[rtopicuri].indexOf(callback); 3211 | if (idx !== -1) { 3212 | removed = callback; 3213 | self._subscriptions[rtopicuri].splice(idx, 1); 3214 | } 3215 | else { 3216 | throw "no callback " + callback + " subscribed on topic " + rtopicuri; 3217 | } 3218 | } 3219 | else { 3220 | removed = self._subscriptions[rtopicuri].slice(); 3221 | self._subscriptions[rtopicuri] = []; 3222 | } 3223 | 3224 | if (self._subscriptions[rtopicuri].length === 0) { 3225 | 3226 | delete self._subscriptions[rtopicuri]; 3227 | 3228 | if (ab._debugpubsub) { 3229 | console.group("WAMP Unsubscribe"); 3230 | console.info(self._wsuri + " [" + self._session_id + "]"); 3231 | console.log(topicuri); 3232 | console.log(removed); 3233 | console.groupEnd(); 3234 | } 3235 | 3236 | var msg = [ab._MESSAGE_TYPEID_UNSUBSCRIBE, topicuri]; 3237 | self._send(msg); 3238 | } 3239 | } 3240 | }; 3241 | 3242 | 3243 | ab.Session.prototype.publish = function () { 3244 | 3245 | var self = this; 3246 | 3247 | var topicuri = arguments[0]; 3248 | var event = arguments[1]; 3249 | 3250 | var excludeMe = null; 3251 | var exclude = null; 3252 | var eligible = null; 3253 | 3254 | var msg = null; 3255 | 3256 | if (arguments.length > 3) { 3257 | 3258 | if (!(arguments[2] instanceof Array)) { 3259 | throw "invalid argument type(s)"; 3260 | } 3261 | if (!(arguments[3] instanceof Array)) { 3262 | throw "invalid argument type(s)"; 3263 | } 3264 | 3265 | exclude = arguments[2]; 3266 | eligible = arguments[3]; 3267 | msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude, eligible]; 3268 | 3269 | } else if (arguments.length > 2) { 3270 | 3271 | if (typeof(arguments[2]) === 'boolean') { 3272 | 3273 | excludeMe = arguments[2]; 3274 | msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, excludeMe]; 3275 | 3276 | } else if (arguments[2] instanceof Array) { 3277 | 3278 | exclude = arguments[2]; 3279 | msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event, exclude]; 3280 | 3281 | } else { 3282 | throw "invalid argument type(s)"; 3283 | } 3284 | 3285 | } else { 3286 | 3287 | msg = [ab._MESSAGE_TYPEID_PUBLISH, topicuri, event]; 3288 | } 3289 | 3290 | if (ab._debugpubsub) { 3291 | console.group("WAMP Publish"); 3292 | console.info(self._wsuri + " [" + self._session_id + "]"); 3293 | console.log(topicuri); 3294 | console.log(event); 3295 | 3296 | if (excludeMe !== null) { 3297 | console.log(excludeMe); 3298 | } else { 3299 | if (exclude !== null) { 3300 | console.log(exclude); 3301 | if (eligible !== null) { 3302 | console.log(eligible); 3303 | } 3304 | } 3305 | } 3306 | console.groupEnd(); 3307 | } 3308 | 3309 | self._send(msg); 3310 | }; 3311 | 3312 | 3313 | // allow both 2-party and 3-party authentication/authorization 3314 | // for 3-party: let C sign, but let both the B and C party authorize 3315 | 3316 | ab.Session.prototype.authreq = function (appkey, extra) { 3317 | return this.call("http://api.wamp.ws/procedure#authreq", appkey, extra); 3318 | }; 3319 | 3320 | ab.Session.prototype.authsign = function (challenge, secret) { 3321 | if (!secret) { 3322 | secret = ""; 3323 | } 3324 | 3325 | return CryptoJS.HmacSHA256(challenge, secret).toString(CryptoJS.enc.Base64); 3326 | }; 3327 | 3328 | ab.Session.prototype.auth = function (signature) { 3329 | return this.call("http://api.wamp.ws/procedure#auth", signature); 3330 | }; 3331 | 3332 | 3333 | ab._connect = function (peer) { 3334 | 3335 | // establish session to WAMP server 3336 | var sess = new ab.Session(peer.wsuri, 3337 | 3338 | // fired when session has been opened 3339 | function() { 3340 | 3341 | peer.connects += 1; 3342 | peer.retryCount = 0; 3343 | 3344 | // we are connected .. do awesome stuff! 3345 | peer.onConnect(sess); 3346 | }, 3347 | 3348 | // fired when session has been closed 3349 | function(code, reason) { 3350 | 3351 | var stop = null; 3352 | 3353 | switch (code) { 3354 | 3355 | case ab.CONNECTION_CLOSED: 3356 | // the session was closed by the app 3357 | peer.onHangup(code, "Connection was closed properly [" + reason + "]"); 3358 | break; 3359 | 3360 | case ab.CONNECTION_UNSUPPORTED: 3361 | // fatal: we miss our WebSocket object! 3362 | peer.onHangup(code, "Browser does not support WebSocket."); 3363 | break; 3364 | 3365 | case ab.CONNECTION_UNREACHABLE: 3366 | 3367 | peer.retryCount += 1; 3368 | 3369 | if (peer.connects === 0) { 3370 | 3371 | // the connection could not be established in the first place 3372 | // which likely means invalid server WS URI or such things 3373 | peer.onHangup(code, "Connection could not be established."); 3374 | 3375 | } else { 3376 | 3377 | // the connection was established at least once successfully, 3378 | // but now lost .. sane thing is to try automatic reconnects 3379 | if (peer.retryCount <= peer.options.maxRetries) { 3380 | 3381 | // notify the app of scheduled reconnect 3382 | stop = peer.onHangup(ab.CONNECTION_UNREACHABLE_SCHEDULED_RECONNECT, 3383 | "Connection unreachable - scheduled reconnect to occur in " + (peer.options.retryDelay / 1000) + " second(s) - attempt " + peer.retryCount + " of " + peer.options.maxRetries + ".", 3384 | {delay: peer.options.retryDelay, 3385 | retries: peer.retryCount, 3386 | maxretries: peer.options.maxRetries}); 3387 | 3388 | if (!stop) { 3389 | if (ab._debugconnect) { 3390 | console.log("Connection unreachable - retrying (" + peer.retryCount + ") .."); 3391 | } 3392 | window.setTimeout(function () { 3393 | ab._connect(peer); 3394 | }, peer.options.retryDelay); 3395 | } else { 3396 | if (ab._debugconnect) { 3397 | console.log("Connection unreachable - retrying stopped by app"); 3398 | } 3399 | peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Number of connection retries exceeded."); 3400 | } 3401 | 3402 | } else { 3403 | peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Number of connection retries exceeded."); 3404 | } 3405 | } 3406 | break; 3407 | 3408 | case ab.CONNECTION_LOST: 3409 | 3410 | peer.retryCount += 1; 3411 | 3412 | if (peer.retryCount <= peer.options.maxRetries) { 3413 | 3414 | // notify the app of scheduled reconnect 3415 | stop = peer.onHangup(ab.CONNECTION_LOST_SCHEDULED_RECONNECT, 3416 | "Connection lost - scheduled " + peer.retryCount + "th reconnect to occur in " + (peer.options.retryDelay / 1000) + " second(s).", 3417 | {delay: peer.options.retryDelay, 3418 | retries: peer.retryCount, 3419 | maxretries: peer.options.maxRetries}); 3420 | 3421 | if (!stop) { 3422 | if (ab._debugconnect) { 3423 | console.log("Connection lost - retrying (" + peer.retryCount + ") .."); 3424 | } 3425 | window.setTimeout(function () { 3426 | ab._connect(peer); 3427 | }, peer.options.retryDelay); 3428 | } else { 3429 | if (ab._debugconnect) { 3430 | console.log("Connection lost - retrying stopped by app"); 3431 | } 3432 | peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Connection lost."); 3433 | } 3434 | } else { 3435 | peer.onHangup(ab.CONNECTION_RETRIES_EXCEEDED, "Connection lost."); 3436 | } 3437 | break; 3438 | 3439 | default: 3440 | throw "unhandled close code in ab._connect"; 3441 | } 3442 | }, 3443 | 3444 | peer.options // forward options to session class for specific WS/WAMP options 3445 | ); 3446 | }; 3447 | 3448 | 3449 | ab.connect = function (wsuri, onconnect, onhangup, options) { 3450 | 3451 | var peer = {}; 3452 | peer.wsuri = wsuri; 3453 | 3454 | if (!options) { 3455 | peer.options = {}; 3456 | } else { 3457 | peer.options = options; 3458 | } 3459 | 3460 | if (peer.options.retryDelay === undefined) { 3461 | peer.options.retryDelay = 5000; 3462 | } 3463 | 3464 | if (peer.options.maxRetries === undefined) { 3465 | peer.options.maxRetries = 10; 3466 | } 3467 | 3468 | if (peer.options.skipSubprotocolCheck === undefined) { 3469 | peer.options.skipSubprotocolCheck = false; 3470 | } 3471 | 3472 | if (peer.options.skipSubprotocolAnnounce === undefined) { 3473 | peer.options.skipSubprotocolAnnounce = false; 3474 | } 3475 | 3476 | if (!onconnect) { 3477 | throw "onConnect handler required!"; 3478 | } else { 3479 | peer.onConnect = onconnect; 3480 | } 3481 | 3482 | if (!onhangup) { 3483 | peer.onHangup = function (code, reason, detail) { 3484 | if (ab._debugconnect) { 3485 | console.log(code, reason, detail); 3486 | } 3487 | }; 3488 | } else { 3489 | peer.onHangup = onhangup; 3490 | } 3491 | 3492 | peer.connects = 0; // total number of successful connects 3493 | peer.retryCount = 0; // number of retries since last successful connect 3494 | 3495 | ab._connect(peer); 3496 | }; 3497 | 3498 | 3499 | ab.launch = function (appConfig, onOpen, onClose) { 3500 | 3501 | function Rpc(session, uri) { 3502 | return function() { 3503 | var args = [uri]; 3504 | for (var j = 0; j < arguments.length; ++j) { 3505 | args.push(arguments[j]); 3506 | } 3507 | //arguments.unshift(uri); 3508 | return ab.Session.prototype.call.apply(session, args); 3509 | }; 3510 | } 3511 | 3512 | function createApi(session, perms) { 3513 | session.api = {}; 3514 | for (var i = 0; i < perms.rpc.length; ++i) { 3515 | var uri = perms.rpc[i].uri; 3516 | 3517 | var _method = uri.split("#")[1]; 3518 | var _class = uri.split("#")[0].split("/"); 3519 | _class = _class[_class.length - 1]; 3520 | 3521 | if (!(_class in session.api)) { 3522 | session.api[_class] = {}; 3523 | } 3524 | 3525 | session.api[_class][_method] = new Rpc(session, uri); 3526 | } 3527 | } 3528 | 3529 | ab.connect(appConfig.wsuri, 3530 | 3531 | // connection established handler 3532 | function (session) { 3533 | if (!appConfig.appkey || appConfig.appkey === "") { 3534 | // Authenticate as anonymous .. 3535 | session.authreq().then(function () { 3536 | session.auth().then(function (permissions) { 3537 | //createApi(session, permissions); 3538 | if (onOpen) { 3539 | onOpen(session); 3540 | } else if (ab._debugconnect) { 3541 | session.log('Session opened.'); 3542 | } 3543 | }, session.log); 3544 | }, session.log); 3545 | } else { 3546 | // Authenticate as appkey .. 3547 | session.authreq(appConfig.appkey, appConfig.appextra).then(function (challenge) { 3548 | 3549 | var signature = null; 3550 | 3551 | if (typeof(appConfig.appsecret) === 'function') { 3552 | signature = appConfig.appsecret(challenge); 3553 | } else { 3554 | // derive secret if salted WAMP-CRA 3555 | var secret = ab.deriveKey(appConfig.appsecret, JSON.parse(challenge).authextra); 3556 | 3557 | // direct sign 3558 | signature = session.authsign(challenge, secret); 3559 | } 3560 | 3561 | session.auth(signature).then(function (permissions) { 3562 | //createApi(session, permissions); 3563 | if (onOpen) { 3564 | onOpen(session); 3565 | } else if (ab._debugconnect) { 3566 | session.log('Session opened.'); 3567 | } 3568 | }, session.log); 3569 | }, session.log); 3570 | } 3571 | }, 3572 | 3573 | // connection lost handler 3574 | function (code, reason, detail) { 3575 | if (onClose) { 3576 | onClose(code, reason, detail); 3577 | } else if (ab._debugconnect) { 3578 | ab.log('Session closed.', code, reason, detail); 3579 | } 3580 | }, 3581 | 3582 | // WAMP session config 3583 | appConfig.sessionConfig 3584 | ); 3585 | }; 3586 | 3587 | return ab; 3588 | })); 3589 | 3590 | ab._UA_FIREFOX = new RegExp(".*Firefox/([0-9+]*).*") 3591 | ab._UA_CHROME = new RegExp(".*Chrome/([0-9+]*).*") 3592 | ab._UA_CHROMEFRAME = new RegExp(".*chromeframe/([0-9]*).*") 3593 | ab._UA_WEBKIT = new RegExp(".*AppleWebKit/([0-9+\.]*)\w*.*") 3594 | ab._UA_WEBOS = new RegExp(".*webOS/([0-9+\.]*)\w*.*") 3595 | 3596 | ab._matchRegex = function(s, r) { 3597 | var m = r.exec(s) 3598 | if (m) return m[1] 3599 | return m 3600 | }; 3601 | 3602 | ab.lookupWsSupport = function() { 3603 | var ua = navigator.userAgent; 3604 | 3605 | // Internet Explorer 3606 | if (ua.indexOf("MSIE") > -1) { 3607 | if (ua.indexOf("MSIE 10") > -1) 3608 | return [true,true,true] 3609 | if (ua.indexOf("chromeframe") > -1) { 3610 | var v = parseInt(ab._matchRegex(ua, ab._UA_CHROMEFRAME)) 3611 | if (v >= 14) 3612 | return [true,false,true] 3613 | return [false,false,false] 3614 | } 3615 | if (ua.indexOf("MSIE 8") > -1 || ua.indexOf("MSIE 9") > -1) 3616 | return [true,true,true] 3617 | return [false,false,false] 3618 | } 3619 | 3620 | // Firefox 3621 | else if (ua.indexOf("Firefox") > -1) { 3622 | var v = parseInt(ab._matchRegex(ua, ab._UA_FIREFOX)) 3623 | if (v) { 3624 | if (v >= 7) 3625 | return [true,false,true] 3626 | if (v >= 3) 3627 | return [true,true,true] 3628 | return [false,false,true] 3629 | } 3630 | return [false,false,true] 3631 | 3632 | } 3633 | 3634 | // Safari 3635 | else if (ua.indexOf("Safari") > -1 && ua.indexOf("Chrome") == -1) { 3636 | var v = ab._matchRegex(ua, ab._UA_WEBKIT) 3637 | if (v) { 3638 | if (ua.indexOf("Windows") > -1 && v == "534+") // Not sure about this test ~RMH 3639 | return [true,false,true] 3640 | if (ua.indexOf("Macintosh") > -1) { 3641 | v = v.replace("+","").split(".") 3642 | if ((parseInt(v[0]) == 535 && parseInt(v[1]) >= 24) || parseInt(v[0]) > 535) 3643 | return [true,false,true] 3644 | } 3645 | if (ua.indexOf("webOS") > -1) { 3646 | v = ab._matchRegex(ua, ab._UA_WEBOS).split(".") 3647 | if (parseInt(v[0]) == 2) 3648 | return [false,true,true] 3649 | return [false,false,false] 3650 | } 3651 | return [true,true,true] 3652 | } 3653 | return [false,false,false] 3654 | } 3655 | 3656 | // Chrome 3657 | else if (ua.indexOf("Chrome") > -1) { 3658 | var v = parseInt(ab._matchRegex(ua, ab._UA_CHROME)) 3659 | if (v) { 3660 | if (v >= 14) 3661 | return [true,false,true] 3662 | if (v >= 4) 3663 | return [true,true,true] 3664 | return [false,false,true] 3665 | } 3666 | return [false,false,false] 3667 | } 3668 | 3669 | // Android 3670 | else if (ua.indexOf("Android") > -1) { 3671 | // Firefox Mobile 3672 | if (ua.indexOf("Firefox") > -1) 3673 | return [true,false,true] 3674 | // Chrome for Android 3675 | else if (ua.indexOf("CrMo") > -1) 3676 | return [true,false,true] 3677 | // Opera Mobile 3678 | else if (ua.indexOf("Opera") > -1) 3679 | return [false,false,true] 3680 | // Android Browser 3681 | else if (ua.indexOf("CrMo") > -1) 3682 | return [true,true,true] 3683 | return [false,false,false] 3684 | } 3685 | 3686 | // iOS 3687 | else if (ua.indexOf("iPhone") > -1 || ua.indexOf("iPad") > -1 || ua.indexOf("iPod") > -1) 3688 | return [false,false,true] 3689 | 3690 | // Unidentified 3691 | return [false,false,false] 3692 | 3693 | 3694 | }; 3695 | --------------------------------------------------------------------------------