diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..7bbfd373 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "../" +} diff --git a/.gitignore b/.gitignore index 7b98530a..18092522 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ /node_modules/ -.kateproject.d -.kateproject +bower_components .directory .c9 -myftppass .codio .settings \ No newline at end of file diff --git a/README.md b/README.md index 0854bb63..752af703 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,32 @@ # ![Yatta!](https://dadamonad.github.io/files/layout/Yatta_logo.png) [![Build Status](http://layers.dbis.rwth-aachen.de/jenkins/job/Yatta/badge/icon)](http://layers.dbis.rwth-aachen.de/jenkins/job/Yatta/) -A Real-Time web framework that manages concurrency control for arbitrary data types. -Yatta! provides similar functionality as [ShareJs](https://github.com/share/ShareJS) and [OpenCoweb](https://github.com/opencoweb/coweb), -but does not require you to understand how the internals work. The predefined data types provide a simple API to access your shared data types. -Predefined data types: -* Text - [Collaborative Text Editing Example](http://dadamonad.github.io/Yatta/examples/TextEditing/) -* Json - [Tutorial](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/) -* XML - [XML Example](http://dadamonad.github.io/Yatta/examples/XmlExample/) Collaboratively manipulate the dom with native dom-features and jQuery. +Yatta is a framework for optimistic concurrency control and automatic conflict resolution on arbitrary data types. The framework implements a new OT-like concurrency algorithm and provides similar functionality as [ShareJs] and [OpenCoweb]. Yatta was designed to take away the pain from concurrently editing complex data types like Text, Json, and XML. You can find some applications for this framework [here](https://dadamonad.github.io/Yatta/examples/). -Unlike other frameworks, Yatta! supports P2P message propagation and is not bound to a specific communication protocol. +In the future, we want to enable users to implement their own collaborative types. Currently we provide data types for +* Text +* Json +* XML -It is possible to add any communication protocol to Yatta. Currently it supports: -* [PeerJs](http://peerjs.com/) - A WebRTC Framework -* [SimpleWebRTC](http://simplewebrtc.com/) - Another WebRTC Framework (coming soon) -* [IWC](http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication) - Inter-widget Communication +Unlike other frameworks, Yatta supports P2P message propagation and is not bound to a specific communication protocol. Therefore, Yatta is extremely scalable and can be used in a wide range of application scenarios. + +We support several communication protocols as so called *Connectors*. You find a bunch of Connectors in the [Yatta-Connectors](https://github.com/rwth-acis/Yatta-Connectors) repository. Currently supported communication protocols: +* [XMPP-Connector](http://xmpp.org) - Propagates updates in a XMPP multi-user-chat room +* [WebRTC-Connector](http://peerjs.com/) - Propagate updates directly with WebRTC +* [IWC-Connector](http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication) - Inter-widget Communication + +You can use Yatta client-, and server- side. You can get it as via npm, and bower. We even provide a polymer element for Yatta! + +The theoretical advantages over similar frameworks are support for +* .. P2P message propagation and arbitrary communication protocols +* .. arbitrary complex data types +* .. offline editing: Only relevant changes are propagated on rejoin (unimplemented) +* .. AnyUndo: Undo *any* action that was executed in constant time (unimplemented) +* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline. ## Use it! -The [examples](./examples/) provide an excellent starting point for beginners. Also the [API Documentation](http://dadamonad.github.io/Yatta/doc/) could prove to be very helpful. +You find a tutorial, examples, and documentation on the [website](https://dadamonad.github.io/Yatta/). Either clone this git repository, install it with [bower](http://bower.io/), or install it with [npm](https://www.npmjs.org/package/yatta). @@ -29,7 +37,7 @@ bower install Yatta ``` Then you include the libraries directly from the installation folder. ``` - + ``` ### Npm @@ -42,56 +50,26 @@ And use it like this with *npm*: Yatta = require("yatta"); ``` - -## About -Find out more about the concurrent editing problem here -[Cooperation, Concurrency, Conflicts, and Convergence](http://opencoweb.org/ocwdocs/intro/openg.html) and here -[Operational Transformation (OT)](http://en.wikipedia.org/wiki/Operational_transformation) - -My Bachelor Thesis project aim was to develop a P2P OT Framework that enables collaboration on XML documents and supports -[Intention Preservation](http://www3.ntu.edu.sg/home/czsun/projects/otfaq/#intentionPreservation). -After some time I realized that OT has significant drawbacks in P2P environments. - -With my gained experiences I came up with a new approach. I named it *Yata* - Yet Another Transformation Approach. -It enables concurrent editing with the following space and time properties: -* Time complexity: O(S), whereby S is the number of operations that are inserted concurrently at the same position (no transformation against operations that happen on different positions). -* Space complexity = O(|Document|), whereby |Document| is the size of the shared document. - -This means that my approach beats all OT time complexities. Furthermore, Yatta has a very strict definition of Intention Preservation, and I was able to -show that it is never violated. - -Another advantage of Yata is that propagated messages are very small. -Background: In Real-Time P2P OT algorithms you have to send a state-vector with each message that defines the state of the History Buffer -on which the operation was created. This is not necessary in Yata. - -The downside of this approach is that the History Buffer holds at least as many operations as there are characters in the document. -In contrast, an OT algorithm can have an empty History Buffer while the document size is very big. - -Eventually (after my thesis), I will publish more information about Yata. - -So, how did I come up with the name for the implementation (Yatta! is not Yata)? -Yatta! means "I did it!" in Japanese. You scream it when you accomplish something (for proper application I refer to the Yatta-man in [Heroes](http://heroeswiki.com/Yatta!)). -There is also this awesome video on the Internet that will change your life [Yatta](https://www.youtube.com/watch?v=kL5DDSglM_s). - ## Status Yatta! is still in an early development phase. Don't expect that everything is working fine. But I would become really motivated if you gave me some feedback :) ([github](https://github.com/DadaMonad/Yatta/issues)). ### Current Issues -* HTML editable tag -* More efficient representation of text. -* Use a better data structure for the History Buffer - it should be possible to use Arrays. -* SimpleRTC support - +* The History Buffer should be able to store operations in a database +* Documentation +* Reimplement support for XML as a data type +* Custom data types ## Support -Please report _any_ issues to the [Github issue page](https://github.com/DadaMonad/Yatta/issues)! -I would appreciate if developers gave me feedback on how _convenient_ the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers, -please state function name, and sample parameters. However, there are browser-specific features, that Yatta won't support. +Please report _any_ issues to the [Github issue page](https://github.com/DadaMonad/Yatta/issues)! +I would appreciate if developers give me feedback on how _convenient_ the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers, please state function name, and sample parameters. However, there are browser-specific features, that Yatta won't support. ## License Yatta! is licensed under the [MIT License](./LICENSE.txt). +[ShareJs]: https://github.com/share/ShareJS +[OpenCoweb]: https://github.com/opencoweb/coweb + diff --git a/bower.json b/bower.json index 24ad22b4..0fc4fc65 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,16 @@ { "name": "Yatta", - "version": "0.0.7", + "version": "0.2.0", "homepage": "https://github.com/DadaMonad/Yatta", "authors": [ "Kevin Jahns " ], "description": "A Framework that enables Real-Time collaboration on arbitrary data structures.", - "main": "./build/**", + "main": [ + "./yatta.js", + "./yatta-element.html", + "./build/node/yatta.js" + ], "keywords": [ "OT", "collaboration", @@ -20,14 +24,10 @@ "node_modules", "bower_components", "test", - "Gruntfile.coffee", "extras", - "tests" + "test" ], "dependencies": { - "peerjs": "~0.3.14", - "polymer": "~0.5.2", - "paper-slider": "Polymer/paper-slider#~0.5.2", - "webcomponentsjs": "~0.5.2" + "polymer": "Polymer/polymer#~0.5.3" } } diff --git a/bower_components/connector/.bower.json b/bower_components/connector/.bower.json deleted file mode 100644 index 619f5f69..00000000 --- a/bower_components/connector/.bower.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "connector", - "authors": [ - "Kevin Jahns " - ], - "description": "Connect to other users via a generic connector. The interface is standardized, so you can use other connectors without changing your code.", - "main": [ - "peerjs-connector/peerjs-connector.min.js", - "peerjs-connector/peerjs-connector.html" - ], - "moduleType": [ - "globals", - "node" - ], - "keywords": [ - "peerjs" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "test", - "tests" - ], - "dependencies": { - "polymer": "Polymer/polymer#~0.5.1", - "peerjs": "~0.3.14" - }, - "homepage": "https://github.com/DadaMonad/Connector", - "_release": "27a5fbf2af", - "_resolution": { - "type": "branch", - "branch": "master", - "commit": "27a5fbf2af5c336df0dc4b27adbe43a0959bbdb1" - }, - "_source": "git://github.com/DadaMonad/Connector.git", - "_target": "*", - "_originalSource": "DadaMonad/Connector" -} \ No newline at end of file diff --git a/bower_components/connector/README.md b/bower_components/connector/README.md deleted file mode 100644 index 33c4bac0..00000000 --- a/bower_components/connector/README.md +++ /dev/null @@ -1,13 +0,0 @@ - -# Connector-Interface -The idea is, to create different implementations of the Connector interface that enable communication within a group. -It has a minimal interface and covers some frequently occuring problems thay you probably will encounter if you use communitcation protocols directly. - -E.g. You can exchange the PeerJs-Connector with the XMPP-Connector only by changing few lines of code. - -It is the communication interface used by [Yatta](https://github.com/DadaMonad/Yatta). - -Currently we have interfaces for: -* PeerJs - -More information about the Connector interface will follow. (Trust the update frequency, this could be a lie) diff --git a/bower_components/connector/bower.json b/bower_components/connector/bower.json deleted file mode 100644 index 0f2e3e89..00000000 --- a/bower_components/connector/bower.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "connector", - "version": "0.0.0", - "authors": [ - "Kevin Jahns " - ], - "description": "Connect to other users via a generic connector. The interface is standardized, so you can use other connectors without changing your code.", - "main": [ - "peerjs-connector/peerjs-connector.min.js", - "peerjs-connector/peerjs-connector.html" - ], - "moduleType": [ - "globals", - "node" - ], - "keywords": [ - "peerjs" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "test", - "tests" - ], - "dependencies": { - "polymer": "Polymer/polymer#~0.5.1", - "peerjs": "~0.3.14" - } -} diff --git a/bower_components/connector/bower_components/core-component-page/README.md b/bower_components/connector/bower_components/core-component-page/README.md deleted file mode 100644 index 7cb18ec7..00000000 --- a/bower_components/connector/bower_components/core-component-page/README.md +++ /dev/null @@ -1,6 +0,0 @@ -core-component-page -=================== - -See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-component-page) for more information. - -Note: this is the vulcanized version of [`core-component-page-dev`](https://github.com/Polymer/core-component-page-dev) (the source). diff --git a/bower_components/connector/bower_components/core-component-page/bowager-logo.png b/bower_components/connector/bower_components/core-component-page/bowager-logo.png deleted file mode 100644 index 76be9fb0..00000000 Binary files a/bower_components/connector/bower_components/core-component-page/bowager-logo.png and /dev/null differ diff --git a/bower_components/connector/bower_components/core-component-page/bower.json b/bower_components/connector/bower_components/core-component-page/bower.json deleted file mode 100644 index 9d3bb7e7..00000000 --- a/bower_components/connector/bower_components/core-component-page/bower.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "core-component-page", - "private": true, - "dependencies": { - "webcomponentsjs": "Polymer/webcomponentsjs#^0.5.0", - "polymer": "Polymer/polymer#^0.5.0" - }, - "version": "0.5.2" -} \ No newline at end of file diff --git a/bower_components/connector/bower_components/core-component-page/core-component-page.html b/bower_components/connector/bower_components/core-component-page/core-component-page.html deleted file mode 100644 index 5c642c77..00000000 --- a/bower_components/connector/bower_components/core-component-page/core-component-page.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - diff --git a/bower_components/connector/bower_components/core-component-page/demo.html b/bower_components/connector/bower_components/core-component-page/demo.html deleted file mode 100644 index 3c414d85..00000000 --- a/bower_components/connector/bower_components/core-component-page/demo.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/bower_components/connector/bower_components/core-component-page/index.html b/bower_components/connector/bower_components/core-component-page/index.html deleted file mode 100644 index 294215a7..00000000 --- a/bower_components/connector/bower_components/core-component-page/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/bower_components/connector/bower_components/peerjs/README.md b/bower_components/connector/bower_components/peerjs/README.md deleted file mode 100644 index 1283b59c..00000000 --- a/bower_components/connector/bower_components/peerjs/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# bower-peerjs - -Install with `bower`: - -```shell -bower install peerjs -``` - -```html - -``` - -## Documentation - -Documentation is available [here](http://peerjs.com/docs). - -## License - -The MIT License - -Copyright (c) 2010-2013 Michelle Bu and Eric Zhang. http://peerjs.com/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/bower_components/connector/bower_components/peerjs/bower.json b/bower_components/connector/bower_components/peerjs/bower.json deleted file mode 100644 index 2816a850..00000000 --- a/bower_components/connector/bower_components/peerjs/bower.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "peerjs", - "version": "0.3.14", - "homepage": "http://peerjs.com", - "authors": [ - "Michelle Bu " - ], - "description": "Simple peer-to-peer data and media using WebRTC.", - "main": "peer.js", - "keywords": [ - "WebRTC", - "peer", - "peerjs", - "p2p" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] -} diff --git a/bower_components/connector/bower_components/peerjs/peer.js b/bower_components/connector/bower_components/peerjs/peer.js deleted file mode 100644 index bad01c9e..00000000 --- a/bower_components/connector/bower_components/peerjs/peer.js +++ /dev/null @@ -1,2939 +0,0 @@ -/*! peerjs build:0.3.14, development. Copyright(c) 2013 Michelle Bu */(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o util.chunkedMTU) { - this._sendChunks(blob); - return; - } - - // DataChannel currently only supports strings. - if (!util.supports.sctp) { - util.blobToBinaryString(blob, function(str) { - self._bufferedSend(str); - }); - } else if (!util.supports.binaryBlob) { - // We only do this if we really need to (e.g. blobs are not supported), - // because this conversion is costly. - util.blobToArrayBuffer(blob, function(ab) { - self._bufferedSend(ab); - }); - } else { - this._bufferedSend(blob); - } - } else { - this._bufferedSend(data); - } -} - -DataConnection.prototype._bufferedSend = function(msg) { - if (this._buffering || !this._trySend(msg)) { - this._buffer.push(msg); - this.bufferSize = this._buffer.length; - } -} - -// Returns true if the send succeeds. -DataConnection.prototype._trySend = function(msg) { - try { - this._dc.send(msg); - } catch (e) { - this._buffering = true; - - var self = this; - setTimeout(function() { - // Try again. - self._buffering = false; - self._tryBuffer(); - }, 100); - return false; - } - return true; -} - -// Try to send the first message in the buffer. -DataConnection.prototype._tryBuffer = function() { - if (this._buffer.length === 0) { - return; - } - - var msg = this._buffer[0]; - - if (this._trySend(msg)) { - this._buffer.shift(); - this.bufferSize = this._buffer.length; - this._tryBuffer(); - } -} - -DataConnection.prototype._sendChunks = function(blob) { - var blobs = util.chunk(blob); - for (var i = 0, ii = blobs.length; i < ii; i += 1) { - var blob = blobs[i]; - this.send(blob, true); - } -} - -DataConnection.prototype.handleMessage = function(message) { - var payload = message.payload; - - switch (message.type) { - case 'ANSWER': - this._peerBrowser = payload.browser; - - // Forward to negotiator - Negotiator.handleSDP(message.type, this, payload.sdp); - break; - case 'CANDIDATE': - Negotiator.handleCandidate(this, payload.candidate); - break; - default: - util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); - break; - } -} - -module.exports = DataConnection; - -},{"./negotiator":5,"./util":8,"eventemitter3":9,"reliable":12}],3:[function(require,module,exports){ -window.Socket = require('./socket'); -window.MediaConnection = require('./mediaconnection'); -window.DataConnection = require('./dataconnection'); -window.Peer = require('./peer'); -window.RTCPeerConnection = require('./adapter').RTCPeerConnection; -window.RTCSessionDescription = require('./adapter').RTCSessionDescription; -window.RTCIceCandidate = require('./adapter').RTCIceCandidate; -window.Negotiator = require('./negotiator'); -window.util = require('./util'); -window.BinaryPack = require('js-binarypack'); - -},{"./adapter":1,"./dataconnection":2,"./mediaconnection":4,"./negotiator":5,"./peer":6,"./socket":7,"./util":8,"js-binarypack":10}],4:[function(require,module,exports){ -var util = require('./util'); -var EventEmitter = require('eventemitter3'); -var Negotiator = require('./negotiator'); - -/** - * Wraps the streaming interface between two Peers. - */ -function MediaConnection(peer, provider, options) { - if (!(this instanceof MediaConnection)) return new MediaConnection(peer, provider, options); - EventEmitter.call(this); - - this.options = util.extend({}, options); - - this.open = false; - this.type = 'media'; - this.peer = peer; - this.provider = provider; - this.metadata = this.options.metadata; - this.localStream = this.options._stream; - - this.id = this.options.connectionId || MediaConnection._idPrefix + util.randomToken(); - if (this.localStream) { - Negotiator.startConnection( - this, - {_stream: this.localStream, originator: true} - ); - } -}; - -util.inherits(MediaConnection, EventEmitter); - -MediaConnection._idPrefix = 'mc_'; - -MediaConnection.prototype.addStream = function(remoteStream) { - util.log('Receiving stream', remoteStream); - - this.remoteStream = remoteStream; - this.emit('stream', remoteStream); // Should we call this `open`? - -}; - -MediaConnection.prototype.handleMessage = function(message) { - var payload = message.payload; - - switch (message.type) { - case 'ANSWER': - // Forward to negotiator - Negotiator.handleSDP(message.type, this, payload.sdp); - this.open = true; - break; - case 'CANDIDATE': - Negotiator.handleCandidate(this, payload.candidate); - break; - default: - util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); - break; - } -} - -MediaConnection.prototype.answer = function(stream) { - if (this.localStream) { - util.warn('Local stream already exists on this MediaConnection. Are you answering a call twice?'); - return; - } - - this.options._payload._stream = stream; - - this.localStream = stream; - Negotiator.startConnection( - this, - this.options._payload - ) - // Retrieve lost messages stored because PeerConnection not set up. - var messages = this.provider._getMessages(this.id); - for (var i = 0, ii = messages.length; i < ii; i += 1) { - this.handleMessage(messages[i]); - } - this.open = true; -}; - -/** - * Exposed functionality for users. - */ - -/** Allows user to close connection. */ -MediaConnection.prototype.close = function() { - if (!this.open) { - return; - } - this.open = false; - Negotiator.cleanup(this); - this.emit('close') -}; - -module.exports = MediaConnection; - -},{"./negotiator":5,"./util":8,"eventemitter3":9}],5:[function(require,module,exports){ -var util = require('./util'); -var RTCPeerConnection = require('./adapter').RTCPeerConnection; -var RTCSessionDescription = require('./adapter').RTCSessionDescription; -var RTCIceCandidate = require('./adapter').RTCIceCandidate; - -/** - * Manages all negotiations between Peers. - */ -var Negotiator = { - pcs: { - data: {}, - media: {} - }, // type => {peerId: {pc_id: pc}}. - //providers: {}, // provider's id => providers (there may be multiple providers/client. - queue: [] // connections that are delayed due to a PC being in use. -} - -Negotiator._idPrefix = 'pc_'; - -/** Returns a PeerConnection object set up correctly (for data, media). */ -Negotiator.startConnection = function(connection, options) { - var pc = Negotiator._getPeerConnection(connection, options); - - if (connection.type === 'media' && options._stream) { - // Add the stream. - pc.addStream(options._stream); - } - - // Set the connection's PC. - connection.pc = connection.peerConnection = pc; - // What do we need to do now? - if (options.originator) { - if (connection.type === 'data') { - // Create the datachannel. - var config = {}; - // Dropping reliable:false support, since it seems to be crashing - // Chrome. - /*if (util.supports.sctp && !options.reliable) { - // If we have canonical reliable support... - config = {maxRetransmits: 0}; - }*/ - // Fallback to ensure older browsers don't crash. - if (!util.supports.sctp) { - config = {reliable: options.reliable}; - } - var dc = pc.createDataChannel(connection.label, config); - connection.initialize(dc); - } - - if (!util.supports.onnegotiationneeded) { - Negotiator._makeOffer(connection); - } - } else { - Negotiator.handleSDP('OFFER', connection, options.sdp); - } -} - -Negotiator._getPeerConnection = function(connection, options) { - if (!Negotiator.pcs[connection.type]) { - util.error(connection.type + ' is not a valid connection type. Maybe you overrode the `type` property somewhere.'); - } - - if (!Negotiator.pcs[connection.type][connection.peer]) { - Negotiator.pcs[connection.type][connection.peer] = {}; - } - var peerConnections = Negotiator.pcs[connection.type][connection.peer]; - - var pc; - // Not multiplexing while FF and Chrome have not-great support for it. - /*if (options.multiplex) { - ids = Object.keys(peerConnections); - for (var i = 0, ii = ids.length; i < ii; i += 1) { - pc = peerConnections[ids[i]]; - if (pc.signalingState === 'stable') { - break; // We can go ahead and use this PC. - } - } - } else */ - if (options.pc) { // Simplest case: PC id already provided for us. - pc = Negotiator.pcs[connection.type][connection.peer][options.pc]; - } - - if (!pc || pc.signalingState !== 'stable') { - pc = Negotiator._startPeerConnection(connection); - } - return pc; -} - -/* -Negotiator._addProvider = function(provider) { - if ((!provider.id && !provider.disconnected) || !provider.socket.open) { - // Wait for provider to obtain an ID. - provider.on('open', function(id) { - Negotiator._addProvider(provider); - }); - } else { - Negotiator.providers[provider.id] = provider; - } -}*/ - - -/** Start a PC. */ -Negotiator._startPeerConnection = function(connection) { - util.log('Creating RTCPeerConnection.'); - - var id = Negotiator._idPrefix + util.randomToken(); - var optional = {}; - - if (connection.type === 'data' && !util.supports.sctp) { - optional = {optional: [{RtpDataChannels: true}]}; - } else if (connection.type === 'media') { - // Interop req for chrome. - optional = {optional: [{DtlsSrtpKeyAgreement: true}]}; - } - - var pc = new RTCPeerConnection(connection.provider.options.config, optional); - Negotiator.pcs[connection.type][connection.peer][id] = pc; - - Negotiator._setupListeners(connection, pc, id); - - return pc; -} - -/** Set up various WebRTC listeners. */ -Negotiator._setupListeners = function(connection, pc, pc_id) { - var peerId = connection.peer; - var connectionId = connection.id; - var provider = connection.provider; - - // ICE CANDIDATES. - util.log('Listening for ICE candidates.'); - pc.onicecandidate = function(evt) { - if (evt.candidate) { - util.log('Received ICE candidates for:', connection.peer); - provider.socket.send({ - type: 'CANDIDATE', - payload: { - candidate: evt.candidate, - type: connection.type, - connectionId: connection.id - }, - dst: peerId - }); - } - }; - - pc.oniceconnectionstatechange = function() { - switch (pc.iceConnectionState) { - case 'disconnected': - case 'failed': - util.log('iceConnectionState is disconnected, closing connections to ' + peerId); - connection.close(); - break; - case 'completed': - pc.onicecandidate = util.noop; - break; - } - }; - - // Fallback for older Chrome impls. - pc.onicechange = pc.oniceconnectionstatechange; - - // ONNEGOTIATIONNEEDED (Chrome) - util.log('Listening for `negotiationneeded`'); - pc.onnegotiationneeded = function() { - util.log('`negotiationneeded` triggered'); - if (pc.signalingState == 'stable') { - Negotiator._makeOffer(connection); - } else { - util.log('onnegotiationneeded triggered when not stable. Is another connection being established?'); - } - }; - - // DATACONNECTION. - util.log('Listening for data channel'); - // Fired between offer and answer, so options should already be saved - // in the options hash. - pc.ondatachannel = function(evt) { - util.log('Received data channel'); - var dc = evt.channel; - var connection = provider.getConnection(peerId, connectionId); - connection.initialize(dc); - }; - - // MEDIACONNECTION. - util.log('Listening for remote stream'); - pc.onaddstream = function(evt) { - util.log('Received remote stream'); - var stream = evt.stream; - var connection = provider.getConnection(peerId, connectionId); - // 10/10/2014: looks like in Chrome 38, onaddstream is triggered after - // setting the remote description. Our connection object in these cases - // is actually a DATA connection, so addStream fails. - // TODO: This is hopefully just a temporary fix. We should try to - // understand why this is happening. - if (connection.type === 'media') { - connection.addStream(stream); - } - }; -} - -Negotiator.cleanup = function(connection) { - util.log('Cleaning up PeerConnection to ' + connection.peer); - - var pc = connection.pc; - - if (!!pc && (pc.readyState !== 'closed' || pc.signalingState !== 'closed')) { - pc.close(); - connection.pc = null; - } -} - -Negotiator._makeOffer = function(connection) { - var pc = connection.pc; - pc.createOffer(function(offer) { - util.log('Created offer.'); - - if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { - offer.sdp = Reliable.higherBandwidthSDP(offer.sdp); - } - - pc.setLocalDescription(offer, function() { - util.log('Set localDescription: offer', 'for:', connection.peer); - connection.provider.socket.send({ - type: 'OFFER', - payload: { - sdp: offer, - type: connection.type, - label: connection.label, - connectionId: connection.id, - reliable: connection.reliable, - serialization: connection.serialization, - metadata: connection.metadata, - browser: util.browser - }, - dst: connection.peer - }); - }, function(err) { - connection.provider.emitError('webrtc', err); - util.log('Failed to setLocalDescription, ', err); - }); - }, function(err) { - connection.provider.emitError('webrtc', err); - util.log('Failed to createOffer, ', err); - }, connection.options.constraints); -} - -Negotiator._makeAnswer = function(connection) { - var pc = connection.pc; - - pc.createAnswer(function(answer) { - util.log('Created answer.'); - - if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { - answer.sdp = Reliable.higherBandwidthSDP(answer.sdp); - } - - pc.setLocalDescription(answer, function() { - util.log('Set localDescription: answer', 'for:', connection.peer); - connection.provider.socket.send({ - type: 'ANSWER', - payload: { - sdp: answer, - type: connection.type, - connectionId: connection.id, - browser: util.browser - }, - dst: connection.peer - }); - }, function(err) { - connection.provider.emitError('webrtc', err); - util.log('Failed to setLocalDescription, ', err); - }); - }, function(err) { - connection.provider.emitError('webrtc', err); - util.log('Failed to create answer, ', err); - }); -} - -/** Handle an SDP. */ -Negotiator.handleSDP = function(type, connection, sdp) { - sdp = new RTCSessionDescription(sdp); - var pc = connection.pc; - - util.log('Setting remote description', sdp); - pc.setRemoteDescription(sdp, function() { - util.log('Set remoteDescription:', type, 'for:', connection.peer); - - if (type === 'OFFER') { - Negotiator._makeAnswer(connection); - } - }, function(err) { - connection.provider.emitError('webrtc', err); - util.log('Failed to setRemoteDescription, ', err); - }); -} - -/** Handle a candidate. */ -Negotiator.handleCandidate = function(connection, ice) { - var candidate = ice.candidate; - var sdpMLineIndex = ice.sdpMLineIndex; - connection.pc.addIceCandidate(new RTCIceCandidate({ - sdpMLineIndex: sdpMLineIndex, - candidate: candidate - })); - util.log('Added ICE candidate for:', connection.peer); -} - -module.exports = Negotiator; - -},{"./adapter":1,"./util":8}],6:[function(require,module,exports){ -var util = require('./util'); -var EventEmitter = require('eventemitter3'); -var Socket = require('./socket'); -var MediaConnection = require('./mediaconnection'); -var DataConnection = require('./dataconnection'); - -/** - * A peer who can initiate connections with other peers. - */ -function Peer(id, options) { - if (!(this instanceof Peer)) return new Peer(id, options); - EventEmitter.call(this); - - // Deal with overloading - if (id && id.constructor == Object) { - options = id; - id = undefined; - } else if (id) { - // Ensure id is a string - id = id.toString(); - } - // - - // Configurize options - options = util.extend({ - debug: 0, // 1: Errors, 2: Warnings, 3: All logs - host: util.CLOUD_HOST, - port: util.CLOUD_PORT, - key: 'peerjs', - path: '/', - token: util.randomToken(), - config: util.defaultConfig - }, options); - this.options = options; - // Detect relative URL host. - if (options.host === '/') { - options.host = window.location.hostname; - } - // Set path correctly. - if (options.path[0] !== '/') { - options.path = '/' + options.path; - } - if (options.path[options.path.length - 1] !== '/') { - options.path += '/'; - } - - // Set whether we use SSL to same as current host - if (options.secure === undefined && options.host !== util.CLOUD_HOST) { - options.secure = util.isSecure(); - } - // Set a custom log function if present - if (options.logFunction) { - util.setLogFunction(options.logFunction); - } - util.setLogLevel(options.debug); - // - - // Sanity checks - // Ensure WebRTC supported - if (!util.supports.audioVideo && !util.supports.data ) { - this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC'); - return; - } - // Ensure alphanumeric id - if (!util.validateId(id)) { - this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid'); - return; - } - // Ensure valid key - if (!util.validateKey(options.key)) { - this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid'); - return; - } - // Ensure not using unsecure cloud server on SSL page - if (options.secure && options.host === '0.peerjs.com') { - this._delayedAbort('ssl-unavailable', - 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.'); - return; - } - // - - // States. - this.destroyed = false; // Connections have been killed - this.disconnected = false; // Connection to PeerServer killed but P2P connections still active - this.open = false; // Sockets and such are not yet open. - // - - // References - this.connections = {}; // DataConnections for this peer. - this._lostMessages = {}; // src => [list of messages] - // - - // Start the server connection - this._initializeServerConnection(); - if (id) { - this._initialize(id); - } else { - this._retrieveId(); - } - // -} - -util.inherits(Peer, EventEmitter); - -// Initialize the 'socket' (which is actually a mix of XHR streaming and -// websockets.) -Peer.prototype._initializeServerConnection = function() { - var self = this; - this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key); - this.socket.on('message', function(data) { - self._handleMessage(data); - }); - this.socket.on('error', function(error) { - self._abort('socket-error', error); - }); - this.socket.on('disconnected', function() { - // If we haven't explicitly disconnected, emit error and disconnect. - if (!self.disconnected) { - self.emitError('network', 'Lost connection to server.'); - self.disconnect(); - } - }); - this.socket.on('close', function() { - // If we haven't explicitly disconnected, emit error. - if (!self.disconnected) { - self._abort('socket-closed', 'Underlying socket is already closed.'); - } - }); -}; - -/** Get a unique ID from the server via XHR. */ -Peer.prototype._retrieveId = function(cb) { - var self = this; - var http = new XMLHttpRequest(); - var protocol = this.options.secure ? 'https://' : 'http://'; - var url = protocol + this.options.host + ':' + this.options.port + - this.options.path + this.options.key + '/id'; - var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); - url += queryString; - - // If there's no ID we need to wait for one before trying to init socket. - http.open('get', url, true); - http.onerror = function(e) { - util.error('Error retrieving ID', e); - var pathError = ''; - if (self.options.path === '/' && self.options.host !== util.CLOUD_HOST) { - pathError = ' If you passed in a `path` to your self-hosted PeerServer, ' + - 'you\'ll also need to pass in that same path when creating a new ' + - 'Peer.'; - } - self._abort('server-error', 'Could not get an ID from the server.' + pathError); - }; - http.onreadystatechange = function() { - if (http.readyState !== 4) { - return; - } - if (http.status !== 200) { - http.onerror(); - return; - } - self._initialize(http.responseText); - }; - http.send(null); -}; - -/** Initialize a connection with the server. */ -Peer.prototype._initialize = function(id) { - this.id = id; - this.socket.start(this.id, this.options.token); -}; - -/** Handles messages from the server. */ -Peer.prototype._handleMessage = function(message) { - var type = message.type; - var payload = message.payload; - var peer = message.src; - var connection; - - switch (type) { - case 'OPEN': // The connection to the server is open. - this.emit('open', this.id); - this.open = true; - break; - case 'ERROR': // Server error. - this._abort('server-error', payload.msg); - break; - case 'ID-TAKEN': // The selected ID is taken. - this._abort('unavailable-id', 'ID `' + this.id + '` is taken'); - break; - case 'INVALID-KEY': // The given API key cannot be found. - this._abort('invalid-key', 'API KEY "' + this.options.key + '" is invalid'); - break; - - // - case 'LEAVE': // Another peer has closed its connection to this peer. - util.log('Received leave message from', peer); - this._cleanupPeer(peer); - break; - - case 'EXPIRE': // The offer sent to a peer has expired without response. - this.emitError('peer-unavailable', 'Could not connect to peer ' + peer); - break; - case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option. - var connectionId = payload.connectionId; - connection = this.getConnection(peer, connectionId); - - if (connection) { - util.warn('Offer received for existing Connection ID:', connectionId); - //connection.handleMessage(message); - } else { - // Create a new connection. - if (payload.type === 'media') { - connection = new MediaConnection(peer, this, { - connectionId: connectionId, - _payload: payload, - metadata: payload.metadata - }); - this._addConnection(peer, connection); - this.emit('call', connection); - } else if (payload.type === 'data') { - connection = new DataConnection(peer, this, { - connectionId: connectionId, - _payload: payload, - metadata: payload.metadata, - label: payload.label, - serialization: payload.serialization, - reliable: payload.reliable - }); - this._addConnection(peer, connection); - this.emit('connection', connection); - } else { - util.warn('Received malformed connection type:', payload.type); - return; - } - // Find messages. - var messages = this._getMessages(connectionId); - for (var i = 0, ii = messages.length; i < ii; i += 1) { - connection.handleMessage(messages[i]); - } - } - break; - default: - if (!payload) { - util.warn('You received a malformed message from ' + peer + ' of type ' + type); - return; - } - - var id = payload.connectionId; - connection = this.getConnection(peer, id); - - if (connection && connection.pc) { - // Pass it on. - connection.handleMessage(message); - } else if (id) { - // Store for possible later use - this._storeMessage(id, message); - } else { - util.warn('You received an unrecognized message:', message); - } - break; - } -}; - -/** Stores messages without a set up connection, to be claimed later. */ -Peer.prototype._storeMessage = function(connectionId, message) { - if (!this._lostMessages[connectionId]) { - this._lostMessages[connectionId] = []; - } - this._lostMessages[connectionId].push(message); -}; - -/** Retrieve messages from lost message store */ -Peer.prototype._getMessages = function(connectionId) { - var messages = this._lostMessages[connectionId]; - if (messages) { - delete this._lostMessages[connectionId]; - return messages; - } else { - return []; - } -}; - -/** - * Returns a DataConnection to the specified peer. See documentation for a - * complete list of options. - */ -Peer.prototype.connect = function(peer, options) { - if (this.disconnected) { - util.warn('You cannot connect to a new Peer because you called ' + - '.disconnect() on this Peer and ended your connection with the ' + - 'server. You can create a new Peer to reconnect, or call reconnect ' + - 'on this peer if you believe its ID to still be available.'); - this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.'); - return; - } - var connection = new DataConnection(peer, this, options); - this._addConnection(peer, connection); - return connection; -}; - -/** - * Returns a MediaConnection to the specified peer. See documentation for a - * complete list of options. - */ -Peer.prototype.call = function(peer, stream, options) { - if (this.disconnected) { - util.warn('You cannot connect to a new Peer because you called ' + - '.disconnect() on this Peer and ended your connection with the ' + - 'server. You can create a new Peer to reconnect.'); - this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.'); - return; - } - if (!stream) { - util.error('To call a peer, you must provide a stream from your browser\'s `getUserMedia`.'); - return; - } - options = options || {}; - options._stream = stream; - var call = new MediaConnection(peer, this, options); - this._addConnection(peer, call); - return call; -}; - -/** Add a data/media connection to this peer. */ -Peer.prototype._addConnection = function(peer, connection) { - if (!this.connections[peer]) { - this.connections[peer] = []; - } - this.connections[peer].push(connection); -}; - -/** Retrieve a data/media connection for this peer. */ -Peer.prototype.getConnection = function(peer, id) { - var connections = this.connections[peer]; - if (!connections) { - return null; - } - for (var i = 0, ii = connections.length; i < ii; i++) { - if (connections[i].id === id) { - return connections[i]; - } - } - return null; -}; - -Peer.prototype._delayedAbort = function(type, message) { - var self = this; - util.setZeroTimeout(function(){ - self._abort(type, message); - }); -}; - -/** - * Destroys the Peer and emits an error message. - * The Peer is not destroyed if it's in a disconnected state, in which case - * it retains its disconnected state and its existing connections. - */ -Peer.prototype._abort = function(type, message) { - util.error('Aborting!'); - if (!this._lastServerId) { - this.destroy(); - } else { - this.disconnect(); - } - this.emitError(type, message); -}; - -/** Emits a typed error message. */ -Peer.prototype.emitError = function(type, err) { - util.error('Error:', err); - if (typeof err === 'string') { - err = new Error(err); - } - err.type = type; - this.emit('error', err); -}; - -/** - * Destroys the Peer: closes all active connections as well as the connection - * to the server. - * Warning: The peer can no longer create or accept connections after being - * destroyed. - */ -Peer.prototype.destroy = function() { - if (!this.destroyed) { - this._cleanup(); - this.disconnect(); - this.destroyed = true; - } -}; - - -/** Disconnects every connection on this peer. */ -Peer.prototype._cleanup = function() { - if (this.connections) { - var peers = Object.keys(this.connections); - for (var i = 0, ii = peers.length; i < ii; i++) { - this._cleanupPeer(peers[i]); - } - } - this.emit('close'); -}; - -/** Closes all connections to this peer. */ -Peer.prototype._cleanupPeer = function(peer) { - var connections = this.connections[peer]; - for (var j = 0, jj = connections.length; j < jj; j += 1) { - connections[j].close(); - } -}; - -/** - * Disconnects the Peer's connection to the PeerServer. Does not close any - * active connections. - * Warning: The peer can no longer create or accept connections after being - * disconnected. It also cannot reconnect to the server. - */ -Peer.prototype.disconnect = function() { - var self = this; - util.setZeroTimeout(function(){ - if (!self.disconnected) { - self.disconnected = true; - self.open = false; - if (self.socket) { - self.socket.close(); - } - self.emit('disconnected', self.id); - self._lastServerId = self.id; - self.id = null; - } - }); -}; - -/** Attempts to reconnect with the same ID. */ -Peer.prototype.reconnect = function() { - if (this.disconnected && !this.destroyed) { - util.log('Attempting reconnection to server with ID ' + this._lastServerId); - this.disconnected = false; - this._initializeServerConnection(); - this._initialize(this._lastServerId); - } else if (this.destroyed) { - throw new Error('This peer cannot reconnect to the server. It has already been destroyed.'); - } else if (!this.disconnected && !this.open) { - // Do nothing. We're still connecting the first time. - util.error('In a hurry? We\'re still trying to make the initial connection!'); - } else { - throw new Error('Peer ' + this.id + ' cannot reconnect because it is not disconnected from the server!'); - } -}; - -/** - * Get a list of available peer IDs. If you're running your own server, you'll - * want to set allow_discovery: true in the PeerServer options. If you're using - * the cloud server, email team@peerjs.com to get the functionality enabled for - * your key. - */ -Peer.prototype.listAllPeers = function(cb) { - cb = cb || function() {}; - var self = this; - var http = new XMLHttpRequest(); - var protocol = this.options.secure ? 'https://' : 'http://'; - var url = protocol + this.options.host + ':' + this.options.port + - this.options.path + this.options.key + '/peers'; - var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); - url += queryString; - - // If there's no ID we need to wait for one before trying to init socket. - http.open('get', url, true); - http.onerror = function(e) { - self._abort('server-error', 'Could not get peers from the server.'); - cb([]); - }; - http.onreadystatechange = function() { - if (http.readyState !== 4) { - return; - } - if (http.status === 401) { - var helpfulError = ''; - if (self.options.host !== util.CLOUD_HOST) { - helpfulError = 'It looks like you\'re using the cloud server. You can email ' + - 'team@peerjs.com to enable peer listing for your API key.'; - } else { - helpfulError = 'You need to enable `allow_discovery` on your self-hosted ' + - 'PeerServer to use this feature.'; - } - cb([]); - throw new Error('It doesn\'t look like you have permission to list peers IDs. ' + helpfulError); - } else if (http.status !== 200) { - cb([]); - } else { - cb(JSON.parse(http.responseText)); - } - }; - http.send(null); -}; - -module.exports = Peer; - -},{"./dataconnection":2,"./mediaconnection":4,"./socket":7,"./util":8,"eventemitter3":9}],7:[function(require,module,exports){ -var util = require('./util'); -var EventEmitter = require('eventemitter3'); - -/** - * An abstraction on top of WebSockets and XHR streaming to provide fastest - * possible connection for peers. - */ -function Socket(secure, host, port, path, key) { - if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key); - - EventEmitter.call(this); - - // Disconnected manually. - this.disconnected = false; - this._queue = []; - - var httpProtocol = secure ? 'https://' : 'http://'; - var wsProtocol = secure ? 'wss://' : 'ws://'; - this._httpUrl = httpProtocol + host + ':' + port + path + key; - this._wsUrl = wsProtocol + host + ':' + port + path + 'peerjs?key=' + key; -} - -util.inherits(Socket, EventEmitter); - - -/** Check in with ID or get one from server. */ -Socket.prototype.start = function(id, token) { - this.id = id; - - this._httpUrl += '/' + id + '/' + token; - this._wsUrl += '&id=' + id + '&token=' + token; - - this._startXhrStream(); - this._startWebSocket(); -} - - -/** Start up websocket communications. */ -Socket.prototype._startWebSocket = function(id) { - var self = this; - - if (this._socket) { - return; - } - - this._socket = new WebSocket(this._wsUrl); - - this._socket.onmessage = function(event) { - try { - var data = JSON.parse(event.data); - } catch(e) { - util.log('Invalid server message', event.data); - return; - } - self.emit('message', data); - }; - - this._socket.onclose = function(event) { - util.log('Socket closed.'); - self.disconnected = true; - self.emit('disconnected'); - }; - - // Take care of the queue of connections if necessary and make sure Peer knows - // socket is open. - this._socket.onopen = function() { - if (self._timeout) { - clearTimeout(self._timeout); - setTimeout(function(){ - self._http.abort(); - self._http = null; - }, 5000); - } - self._sendQueuedMessages(); - util.log('Socket open'); - }; -} - -/** Start XHR streaming. */ -Socket.prototype._startXhrStream = function(n) { - try { - var self = this; - this._http = new XMLHttpRequest(); - this._http._index = 1; - this._http._streamIndex = n || 0; - this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true); - this._http.onerror = function() { - // If we get an error, likely something went wrong. - // Stop streaming. - clearTimeout(self._timeout); - self.emit('disconnected'); - } - this._http.onreadystatechange = function() { - if (this.readyState == 2 && this.old) { - this.old.abort(); - delete this.old; - } else if (this.readyState > 2 && this.status === 200 && this.responseText) { - self._handleStream(this); - } - }; - this._http.send(null); - this._setHTTPTimeout(); - } catch(e) { - util.log('XMLHttpRequest not available; defaulting to WebSockets'); - } -} - - -/** Handles onreadystatechange response as a stream. */ -Socket.prototype._handleStream = function(http) { - // 3 and 4 are loading/done state. All others are not relevant. - var messages = http.responseText.split('\n'); - - // Check to see if anything needs to be processed on buffer. - if (http._buffer) { - while (http._buffer.length > 0) { - var index = http._buffer.shift(); - var bufferedMessage = messages[index]; - try { - bufferedMessage = JSON.parse(bufferedMessage); - } catch(e) { - http._buffer.shift(index); - break; - } - this.emit('message', bufferedMessage); - } - } - - var message = messages[http._index]; - if (message) { - http._index += 1; - // Buffering--this message is incomplete and we'll get to it next time. - // This checks if the httpResponse ended in a `\n`, in which case the last - // element of messages should be the empty string. - if (http._index === messages.length) { - if (!http._buffer) { - http._buffer = []; - } - http._buffer.push(http._index - 1); - } else { - try { - message = JSON.parse(message); - } catch(e) { - util.log('Invalid server message', message); - return; - } - this.emit('message', message); - } - } -} - -Socket.prototype._setHTTPTimeout = function() { - var self = this; - this._timeout = setTimeout(function() { - var old = self._http; - if (!self._wsOpen()) { - self._startXhrStream(old._streamIndex + 1); - self._http.old = old; - } else { - old.abort(); - } - }, 25000); -} - -/** Is the websocket currently open? */ -Socket.prototype._wsOpen = function() { - return this._socket && this._socket.readyState == 1; -} - -/** Send queued messages. */ -Socket.prototype._sendQueuedMessages = function() { - for (var i = 0, ii = this._queue.length; i < ii; i += 1) { - this.send(this._queue[i]); - } -} - -/** Exposed send for DC & Peer. */ -Socket.prototype.send = function(data) { - if (this.disconnected) { - return; - } - - // If we didn't get an ID yet, we can't yet send anything so we should queue - // up these messages. - if (!this.id) { - this._queue.push(data); - return; - } - - if (!data.type) { - this.emit('error', 'Invalid message'); - return; - } - - var message = JSON.stringify(data); - if (this._wsOpen()) { - this._socket.send(message); - } else { - var http = new XMLHttpRequest(); - var url = this._httpUrl + '/' + data.type.toLowerCase(); - http.open('post', url, true); - http.setRequestHeader('Content-Type', 'application/json'); - http.send(message); - } -} - -Socket.prototype.close = function() { - if (!this.disconnected && this._wsOpen()) { - this._socket.close(); - this.disconnected = true; - } -} - -module.exports = Socket; - -},{"./util":8,"eventemitter3":9}],8:[function(require,module,exports){ -var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]}; -var dataCount = 1; - -var BinaryPack = require('js-binarypack'); -var RTCPeerConnection = require('./adapter').RTCPeerConnection; - -var util = { - noop: function() {}, - - CLOUD_HOST: '0.peerjs.com', - CLOUD_PORT: 9000, - - // Browsers that need chunking: - chunkedBrowsers: {'Chrome': 1}, - chunkedMTU: 16300, // The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually. - - // Logging logic - logLevel: 0, - setLogLevel: function(level) { - var debugLevel = parseInt(level, 10); - if (!isNaN(parseInt(level, 10))) { - util.logLevel = debugLevel; - } else { - // If they are using truthy/falsy values for debug - util.logLevel = level ? 3 : 0; - } - util.log = util.warn = util.error = util.noop; - if (util.logLevel > 0) { - util.error = util._printWith('ERROR'); - } - if (util.logLevel > 1) { - util.warn = util._printWith('WARNING'); - } - if (util.logLevel > 2) { - util.log = util._print; - } - }, - setLogFunction: function(fn) { - if (fn.constructor !== Function) { - util.warn('The log function you passed in is not a function. Defaulting to regular logs.'); - } else { - util._print = fn; - } - }, - - _printWith: function(prefix) { - return function() { - var copy = Array.prototype.slice.call(arguments); - copy.unshift(prefix); - util._print.apply(util, copy); - }; - }, - _print: function () { - var err = false; - var copy = Array.prototype.slice.call(arguments); - copy.unshift('PeerJS: '); - for (var i = 0, l = copy.length; i < l; i++){ - if (copy[i] instanceof Error) { - copy[i] = '(' + copy[i].name + ') ' + copy[i].message; - err = true; - } - } - err ? console.error.apply(console, copy) : console.log.apply(console, copy); - }, - // - - // Returns browser-agnostic default config - defaultConfig: defaultConfig, - // - - // Returns the current browser. - browser: (function() { - if (window.mozRTCPeerConnection) { - return 'Firefox'; - } else if (window.webkitRTCPeerConnection) { - return 'Chrome'; - } else if (window.RTCPeerConnection) { - return 'Supported'; - } else { - return 'Unsupported'; - } - })(), - // - - // Lists which features are supported - supports: (function() { - if (typeof RTCPeerConnection === 'undefined') { - return {}; - } - - var data = true; - var audioVideo = true; - - var binaryBlob = false; - var sctp = false; - var onnegotiationneeded = !!window.webkitRTCPeerConnection; - - var pc, dc; - try { - pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); - } catch (e) { - data = false; - audioVideo = false; - } - - if (data) { - try { - dc = pc.createDataChannel('_PEERJSTEST'); - } catch (e) { - data = false; - } - } - - if (data) { - // Binary test - try { - dc.binaryType = 'blob'; - binaryBlob = true; - } catch (e) { - } - - // Reliable test. - // Unfortunately Chrome is a bit unreliable about whether or not they - // support reliable. - var reliablePC = new RTCPeerConnection(defaultConfig, {}); - try { - var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {}); - sctp = reliableDC.reliable; - } catch (e) { - } - reliablePC.close(); - } - - // FIXME: not really the best check... - if (audioVideo) { - audioVideo = !!pc.addStream; - } - - // FIXME: this is not great because in theory it doesn't work for - // av-only browsers (?). - if (!onnegotiationneeded && data) { - // sync default check. - var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); - negotiationPC.onnegotiationneeded = function() { - onnegotiationneeded = true; - // async check. - if (util && util.supports) { - util.supports.onnegotiationneeded = true; - } - }; - negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST'); - - setTimeout(function() { - negotiationPC.close(); - }, 1000); - } - - if (pc) { - pc.close(); - } - - return { - audioVideo: audioVideo, - data: data, - binaryBlob: binaryBlob, - binary: sctp, // deprecated; sctp implies binary support. - reliable: sctp, // deprecated; sctp implies reliable data. - sctp: sctp, - onnegotiationneeded: onnegotiationneeded - }; - }()), - // - - // Ensure alphanumeric ids - validateId: function(id) { - // Allow empty ids - return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id); - }, - - validateKey: function(key) { - // Allow empty keys - return !key || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key); - }, - - - debug: false, - - inherits: function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }, - extend: function(dest, source) { - for(var key in source) { - if(source.hasOwnProperty(key)) { - dest[key] = source[key]; - } - } - return dest; - }, - pack: BinaryPack.pack, - unpack: BinaryPack.unpack, - - log: function () { - if (util.debug) { - var err = false; - var copy = Array.prototype.slice.call(arguments); - copy.unshift('PeerJS: '); - for (var i = 0, l = copy.length; i < l; i++){ - if (copy[i] instanceof Error) { - copy[i] = '(' + copy[i].name + ') ' + copy[i].message; - err = true; - } - } - err ? console.error.apply(console, copy) : console.log.apply(console, copy); - } - }, - - setZeroTimeout: (function(global) { - var timeouts = []; - var messageName = 'zero-timeout-message'; - - // Like setTimeout, but only takes a function argument. There's - // no time argument (always zero) and no arguments (you have to - // use a closure). - function setZeroTimeoutPostMessage(fn) { - timeouts.push(fn); - global.postMessage(messageName, '*'); - } - - function handleMessage(event) { - if (event.source == global && event.data == messageName) { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (timeouts.length) { - timeouts.shift()(); - } - } - } - if (global.addEventListener) { - global.addEventListener('message', handleMessage, true); - } else if (global.attachEvent) { - global.attachEvent('onmessage', handleMessage); - } - return setZeroTimeoutPostMessage; - }(window)), - - // Binary stuff - - // chunks a blob. - chunk: function(bl) { - var chunks = []; - var size = bl.size; - var start = index = 0; - var total = Math.ceil(size / util.chunkedMTU); - while (start < size) { - var end = Math.min(size, start + util.chunkedMTU); - var b = bl.slice(start, end); - - var chunk = { - __peerData: dataCount, - n: index, - data: b, - total: total - }; - - chunks.push(chunk); - - start = end; - index += 1; - } - dataCount += 1; - return chunks; - }, - - blobToArrayBuffer: function(blob, cb){ - var fr = new FileReader(); - fr.onload = function(evt) { - cb(evt.target.result); - }; - fr.readAsArrayBuffer(blob); - }, - blobToBinaryString: function(blob, cb){ - var fr = new FileReader(); - fr.onload = function(evt) { - cb(evt.target.result); - }; - fr.readAsBinaryString(blob); - }, - binaryStringToArrayBuffer: function(binary) { - var byteArray = new Uint8Array(binary.length); - for (var i = 0; i < binary.length; i++) { - byteArray[i] = binary.charCodeAt(i) & 0xff; - } - return byteArray.buffer; - }, - randomToken: function () { - return Math.random().toString(36).substr(2); - }, - // - - isSecure: function() { - return location.protocol === 'https:'; - } -}; - -module.exports = util; - -},{"./adapter":1,"js-binarypack":10}],9:[function(require,module,exports){ -'use strict'; - -/** - * Representation of a single EventEmitter function. - * - * @param {Function} fn Event handler to be called. - * @param {Mixed} context Context for function execution. - * @param {Boolean} once Only emit once - * @api private - */ -function EE(fn, context, once) { - this.fn = fn; - this.context = context; - this.once = once || false; -} - -/** - * Minimal EventEmitter interface that is molded against the Node.js - * EventEmitter interface. - * - * @constructor - * @api public - */ -function EventEmitter() { /* Nothing to set */ } - -/** - * Holds the assigned EventEmitters by name. - * - * @type {Object} - * @private - */ -EventEmitter.prototype._events = undefined; - -/** - * Return a list of assigned event listeners. - * - * @param {String} event The events that should be listed. - * @returns {Array} - * @api public - */ -EventEmitter.prototype.listeners = function listeners(event) { - if (!this._events || !this._events[event]) return []; - - for (var i = 0, l = this._events[event].length, ee = []; i < l; i++) { - ee.push(this._events[event][i].fn); - } - - return ee; -}; - -/** - * Emit an event to all registered event listeners. - * - * @param {String} event The name of the event. - * @returns {Boolean} Indication if we've emitted an event. - * @api public - */ -EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { - if (!this._events || !this._events[event]) return false; - - var listeners = this._events[event] - , length = listeners.length - , len = arguments.length - , ee = listeners[0] - , args - , i, j; - - if (1 === length) { - if (ee.once) this.removeListener(event, ee.fn, true); - - switch (len) { - case 1: return ee.fn.call(ee.context), true; - case 2: return ee.fn.call(ee.context, a1), true; - case 3: return ee.fn.call(ee.context, a1, a2), true; - case 4: return ee.fn.call(ee.context, a1, a2, a3), true; - case 5: return ee.fn.call(ee.context, a1, a2, a3, a4), true; - case 6: return ee.fn.call(ee.context, a1, a2, a3, a4, a5), true; - } - - for (i = 1, args = new Array(len -1); i < len; i++) { - args[i - 1] = arguments[i]; - } - - ee.fn.apply(ee.context, args); - } else { - for (i = 0; i < length; i++) { - if (listeners[i].once) this.removeListener(event, listeners[i].fn, true); - - switch (len) { - case 1: listeners[i].fn.call(listeners[i].context); break; - case 2: listeners[i].fn.call(listeners[i].context, a1); break; - case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; - default: - if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { - args[j - 1] = arguments[j]; - } - - listeners[i].fn.apply(listeners[i].context, args); - } - } - } - - return true; -}; - -/** - * Register a new EventListener for the given event. - * - * @param {String} event Name of the event. - * @param {Functon} fn Callback function. - * @param {Mixed} context The context of the function. - * @api public - */ -EventEmitter.prototype.on = function on(event, fn, context) { - if (!this._events) this._events = {}; - if (!this._events[event]) this._events[event] = []; - this._events[event].push(new EE( fn, context || this )); - - return this; -}; - -/** - * Add an EventListener that's only called once. - * - * @param {String} event Name of the event. - * @param {Function} fn Callback function. - * @param {Mixed} context The context of the function. - * @api public - */ -EventEmitter.prototype.once = function once(event, fn, context) { - if (!this._events) this._events = {}; - if (!this._events[event]) this._events[event] = []; - this._events[event].push(new EE(fn, context || this, true )); - - return this; -}; - -/** - * Remove event listeners. - * - * @param {String} event The event we want to remove. - * @param {Function} fn The listener that we need to find. - * @param {Boolean} once Only remove once listeners. - * @api public - */ -EventEmitter.prototype.removeListener = function removeListener(event, fn, once) { - if (!this._events || !this._events[event]) return this; - - var listeners = this._events[event] - , events = []; - - if (fn) for (var i = 0, length = listeners.length; i < length; i++) { - if (listeners[i].fn !== fn && listeners[i].once !== once) { - events.push(listeners[i]); - } - } - - // - // Reset the array, or remove it completely if we have no more listeners. - // - if (events.length) this._events[event] = events; - else this._events[event] = null; - - return this; -}; - -/** - * Remove all listeners or only the listeners for the specified event. - * - * @param {String} event The event want to remove all listeners for. - * @api public - */ -EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { - if (!this._events) return this; - - if (event) this._events[event] = null; - else this._events = {}; - - return this; -}; - -// -// Alias methods names because people roll like that. -// -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -// -// This function doesn't apply anymore. -// -EventEmitter.prototype.setMaxListeners = function setMaxListeners() { - return this; -}; - -// -// Expose the module. -// -EventEmitter.EventEmitter = EventEmitter; -EventEmitter.EventEmitter2 = EventEmitter; -EventEmitter.EventEmitter3 = EventEmitter; - -if ('object' === typeof module && module.exports) { - module.exports = EventEmitter; -} - -},{}],10:[function(require,module,exports){ -var BufferBuilder = require('./bufferbuilder').BufferBuilder; -var binaryFeatures = require('./bufferbuilder').binaryFeatures; - -var BinaryPack = { - unpack: function(data){ - var unpacker = new Unpacker(data); - return unpacker.unpack(); - }, - pack: function(data){ - var packer = new Packer(); - packer.pack(data); - var buffer = packer.getBuffer(); - return buffer; - } -}; - -module.exports = BinaryPack; - -function Unpacker (data){ - // Data is ArrayBuffer - this.index = 0; - this.dataBuffer = data; - this.dataView = new Uint8Array(this.dataBuffer); - this.length = this.dataBuffer.byteLength; -} - -Unpacker.prototype.unpack = function(){ - var type = this.unpack_uint8(); - if (type < 0x80){ - var positive_fixnum = type; - return positive_fixnum; - } else if ((type ^ 0xe0) < 0x20){ - var negative_fixnum = (type ^ 0xe0) - 0x20; - return negative_fixnum; - } - var size; - if ((size = type ^ 0xa0) <= 0x0f){ - return this.unpack_raw(size); - } else if ((size = type ^ 0xb0) <= 0x0f){ - return this.unpack_string(size); - } else if ((size = type ^ 0x90) <= 0x0f){ - return this.unpack_array(size); - } else if ((size = type ^ 0x80) <= 0x0f){ - return this.unpack_map(size); - } - switch(type){ - case 0xc0: - return null; - case 0xc1: - return undefined; - case 0xc2: - return false; - case 0xc3: - return true; - case 0xca: - return this.unpack_float(); - case 0xcb: - return this.unpack_double(); - case 0xcc: - return this.unpack_uint8(); - case 0xcd: - return this.unpack_uint16(); - case 0xce: - return this.unpack_uint32(); - case 0xcf: - return this.unpack_uint64(); - case 0xd0: - return this.unpack_int8(); - case 0xd1: - return this.unpack_int16(); - case 0xd2: - return this.unpack_int32(); - case 0xd3: - return this.unpack_int64(); - case 0xd4: - return undefined; - case 0xd5: - return undefined; - case 0xd6: - return undefined; - case 0xd7: - return undefined; - case 0xd8: - size = this.unpack_uint16(); - return this.unpack_string(size); - case 0xd9: - size = this.unpack_uint32(); - return this.unpack_string(size); - case 0xda: - size = this.unpack_uint16(); - return this.unpack_raw(size); - case 0xdb: - size = this.unpack_uint32(); - return this.unpack_raw(size); - case 0xdc: - size = this.unpack_uint16(); - return this.unpack_array(size); - case 0xdd: - size = this.unpack_uint32(); - return this.unpack_array(size); - case 0xde: - size = this.unpack_uint16(); - return this.unpack_map(size); - case 0xdf: - size = this.unpack_uint32(); - return this.unpack_map(size); - } -} - -Unpacker.prototype.unpack_uint8 = function(){ - var byte = this.dataView[this.index] & 0xff; - this.index++; - return byte; -}; - -Unpacker.prototype.unpack_uint16 = function(){ - var bytes = this.read(2); - var uint16 = - ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); - this.index += 2; - return uint16; -} - -Unpacker.prototype.unpack_uint32 = function(){ - var bytes = this.read(4); - var uint32 = - ((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]; - this.index += 4; - return uint32; -} - -Unpacker.prototype.unpack_uint64 = function(){ - var bytes = this.read(8); - var uint64 = - ((((((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]) * 256 + - bytes[4]) * 256 + - bytes[5]) * 256 + - bytes[6]) * 256 + - bytes[7]; - this.index += 8; - return uint64; -} - - -Unpacker.prototype.unpack_int8 = function(){ - var uint8 = this.unpack_uint8(); - return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); -}; - -Unpacker.prototype.unpack_int16 = function(){ - var uint16 = this.unpack_uint16(); - return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); -} - -Unpacker.prototype.unpack_int32 = function(){ - var uint32 = this.unpack_uint32(); - return (uint32 < Math.pow(2, 31) ) ? uint32 : - uint32 - Math.pow(2, 32); -} - -Unpacker.prototype.unpack_int64 = function(){ - var uint64 = this.unpack_uint64(); - return (uint64 < Math.pow(2, 63) ) ? uint64 : - uint64 - Math.pow(2, 64); -} - -Unpacker.prototype.unpack_raw = function(size){ - if ( this.length < this.index + size){ - throw new Error('BinaryPackFailure: index is out of range' - + ' ' + this.index + ' ' + size + ' ' + this.length); - } - var buf = this.dataBuffer.slice(this.index, this.index + size); - this.index += size; - - //buf = util.bufferToString(buf); - - return buf; -} - -Unpacker.prototype.unpack_string = function(size){ - var bytes = this.read(size); - var i = 0, str = '', c, code; - while(i < size){ - c = bytes[i]; - if ( c < 128){ - str += String.fromCharCode(c); - i++; - } else if ((c ^ 0xc0) < 32){ - code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); - str += String.fromCharCode(code); - i += 2; - } else { - code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | - (bytes[i+2] & 63); - str += String.fromCharCode(code); - i += 3; - } - } - this.index += size; - return str; -} - -Unpacker.prototype.unpack_array = function(size){ - var objects = new Array(size); - for(var i = 0; i < size ; i++){ - objects[i] = this.unpack(); - } - return objects; -} - -Unpacker.prototype.unpack_map = function(size){ - var map = {}; - for(var i = 0; i < size ; i++){ - var key = this.unpack(); - var value = this.unpack(); - map[key] = value; - } - return map; -} - -Unpacker.prototype.unpack_float = function(){ - var uint32 = this.unpack_uint32(); - var sign = uint32 >> 31; - var exp = ((uint32 >> 23) & 0xff) - 127; - var fraction = ( uint32 & 0x7fffff ) | 0x800000; - return (sign == 0 ? 1 : -1) * - fraction * Math.pow(2, exp - 23); -} - -Unpacker.prototype.unpack_double = function(){ - var h32 = this.unpack_uint32(); - var l32 = this.unpack_uint32(); - var sign = h32 >> 31; - var exp = ((h32 >> 20) & 0x7ff) - 1023; - var hfrac = ( h32 & 0xfffff ) | 0x100000; - var frac = hfrac * Math.pow(2, exp - 20) + - l32 * Math.pow(2, exp - 52); - return (sign == 0 ? 1 : -1) * frac; -} - -Unpacker.prototype.read = function(length){ - var j = this.index; - if (j + length <= this.length) { - return this.dataView.subarray(j, j + length); - } else { - throw new Error('BinaryPackFailure: read index out of range'); - } -} - -function Packer(){ - this.bufferBuilder = new BufferBuilder(); -} - -Packer.prototype.getBuffer = function(){ - return this.bufferBuilder.getBuffer(); -} - -Packer.prototype.pack = function(value){ - var type = typeof(value); - if (type == 'string'){ - this.pack_string(value); - } else if (type == 'number'){ - if (Math.floor(value) === value){ - this.pack_integer(value); - } else{ - this.pack_double(value); - } - } else if (type == 'boolean'){ - if (value === true){ - this.bufferBuilder.append(0xc3); - } else if (value === false){ - this.bufferBuilder.append(0xc2); - } - } else if (type == 'undefined'){ - this.bufferBuilder.append(0xc0); - } else if (type == 'object'){ - if (value === null){ - this.bufferBuilder.append(0xc0); - } else { - var constructor = value.constructor; - if (constructor == Array){ - this.pack_array(value); - } else if (constructor == Blob || constructor == File) { - this.pack_bin(value); - } else if (constructor == ArrayBuffer) { - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value)); - } else { - this.pack_bin(value); - } - } else if ('BYTES_PER_ELEMENT' in value){ - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value.buffer)); - } else { - this.pack_bin(value.buffer); - } - } else if (constructor == Object){ - this.pack_object(value); - } else if (constructor == Date){ - this.pack_string(value.toString()); - } else if (typeof value.toBinaryPack == 'function'){ - this.bufferBuilder.append(value.toBinaryPack()); - } else { - throw new Error('Type "' + constructor.toString() + '" not yet supported'); - } - } - } else { - throw new Error('Type "' + type + '" not yet supported'); - } - this.bufferBuilder.flush(); -} - - -Packer.prototype.pack_bin = function(blob){ - var length = blob.length || blob.byteLength || blob.size; - if (length <= 0x0f){ - this.pack_uint8(0xa0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xda) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdb); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(blob); -} - -Packer.prototype.pack_string = function(str){ - var length = utf8Length(str); - - if (length <= 0x0f){ - this.pack_uint8(0xb0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xd8) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xd9); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(str); -} - -Packer.prototype.pack_array = function(ary){ - var length = ary.length; - if (length <= 0x0f){ - this.pack_uint8(0x90 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xdc) - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdd); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var i = 0; i < length ; i++){ - this.pack(ary[i]); - } -} - -Packer.prototype.pack_integer = function(num){ - if ( -0x20 <= num && num <= 0x7f){ - this.bufferBuilder.append(num & 0xff); - } else if (0x00 <= num && num <= 0xff){ - this.bufferBuilder.append(0xcc); - this.pack_uint8(num); - } else if (-0x80 <= num && num <= 0x7f){ - this.bufferBuilder.append(0xd0); - this.pack_int8(num); - } else if ( 0x0000 <= num && num <= 0xffff){ - this.bufferBuilder.append(0xcd); - this.pack_uint16(num); - } else if (-0x8000 <= num && num <= 0x7fff){ - this.bufferBuilder.append(0xd1); - this.pack_int16(num); - } else if ( 0x00000000 <= num && num <= 0xffffffff){ - this.bufferBuilder.append(0xce); - this.pack_uint32(num); - } else if (-0x80000000 <= num && num <= 0x7fffffff){ - this.bufferBuilder.append(0xd2); - this.pack_int32(num); - } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xd3); - this.pack_int64(num); - } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xcf); - this.pack_uint64(num); - } else{ - throw new Error('Invalid integer'); - } -} - -Packer.prototype.pack_double = function(num){ - var sign = 0; - if (num < 0){ - sign = 1; - num = -num; - } - var exp = Math.floor(Math.log(num) / Math.LN2); - var frac0 = num / Math.pow(2, exp) - 1; - var frac1 = Math.floor(frac0 * Math.pow(2, 52)); - var b32 = Math.pow(2, 32); - var h32 = (sign << 31) | ((exp+1023) << 20) | - (frac1 / b32) & 0x0fffff; - var l32 = frac1 % b32; - this.bufferBuilder.append(0xcb); - this.pack_int32(h32); - this.pack_int32(l32); -} - -Packer.prototype.pack_object = function(obj){ - var keys = Object.keys(obj); - var length = keys.length; - if (length <= 0x0f){ - this.pack_uint8(0x80 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xde); - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdf); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var prop in obj){ - if (obj.hasOwnProperty(prop)){ - this.pack(prop); - this.pack(obj[prop]); - } - } -} - -Packer.prototype.pack_uint8 = function(num){ - this.bufferBuilder.append(num); -} - -Packer.prototype.pack_uint16 = function(num){ - this.bufferBuilder.append(num >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_uint32 = function(num){ - var n = num & 0xffffffff; - this.bufferBuilder.append((n & 0xff000000) >>> 24); - this.bufferBuilder.append((n & 0x00ff0000) >>> 16); - this.bufferBuilder.append((n & 0x0000ff00) >>> 8); - this.bufferBuilder.append((n & 0x000000ff)); -} - -Packer.prototype.pack_uint64 = function(num){ - var high = num / Math.pow(2, 32); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -Packer.prototype.pack_int8 = function(num){ - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int16 = function(num){ - this.bufferBuilder.append((num & 0xff00) >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int32 = function(num){ - this.bufferBuilder.append((num >>> 24) & 0xff); - this.bufferBuilder.append((num & 0x00ff0000) >>> 16); - this.bufferBuilder.append((num & 0x0000ff00) >>> 8); - this.bufferBuilder.append((num & 0x000000ff)); -} - -Packer.prototype.pack_int64 = function(num){ - var high = Math.floor(num / Math.pow(2, 32)); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -function _utf8Replace(m){ - var code = m.charCodeAt(0); - - if(code <= 0x7ff) return '00'; - if(code <= 0xffff) return '000'; - if(code <= 0x1fffff) return '0000'; - if(code <= 0x3ffffff) return '00000'; - return '000000'; -} - -function utf8Length(str){ - if (str.length > 600) { - // Blob method faster for large strings - return (new Blob([str])).size; - } else { - return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; - } -} - -},{"./bufferbuilder":11}],11:[function(require,module,exports){ -var binaryFeatures = {}; -binaryFeatures.useBlobBuilder = (function(){ - try { - new Blob([]); - return false; - } catch (e) { - return true; - } -})(); - -binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ - try { - return (new Blob([new Uint8Array([])])).size === 0; - } catch (e) { - return true; - } -})(); - -module.exports.binaryFeatures = binaryFeatures; -var BlobBuilder = module.exports.BlobBuilder; -if (typeof window != 'undefined') { - BlobBuilder = module.exports.BlobBuilder = window.WebKitBlobBuilder || - window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; -} - -function BufferBuilder(){ - this._pieces = []; - this._parts = []; -} - -BufferBuilder.prototype.append = function(data) { - if(typeof data === 'number') { - this._pieces.push(data); - } else { - this.flush(); - this._parts.push(data); - } -}; - -BufferBuilder.prototype.flush = function() { - if (this._pieces.length > 0) { - var buf = new Uint8Array(this._pieces); - if(!binaryFeatures.useArrayBufferView) { - buf = buf.buffer; - } - this._parts.push(buf); - this._pieces = []; - } -}; - -BufferBuilder.prototype.getBuffer = function() { - this.flush(); - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - for(var i = 0, ii = this._parts.length; i < ii; i++) { - builder.append(this._parts[i]); - } - return builder.getBlob(); - } else { - return new Blob(this._parts); - } -}; - -module.exports.BufferBuilder = BufferBuilder; - -},{}],12:[function(require,module,exports){ -var util = require('./util'); - -/** - * Reliable transfer for Chrome Canary DataChannel impl. - * Author: @michellebu - */ -function Reliable(dc, debug) { - if (!(this instanceof Reliable)) return new Reliable(dc); - this._dc = dc; - - util.debug = debug; - - // Messages sent/received so far. - // id: { ack: n, chunks: [...] } - this._outgoing = {}; - // id: { ack: ['ack', id, n], chunks: [...] } - this._incoming = {}; - this._received = {}; - - // Window size. - this._window = 1000; - // MTU. - this._mtu = 500; - // Interval for setInterval. In ms. - this._interval = 0; - - // Messages sent. - this._count = 0; - - // Outgoing message queue. - this._queue = []; - - this._setupDC(); -}; - -// Send a message reliably. -Reliable.prototype.send = function(msg) { - // Determine if chunking is necessary. - var bl = util.pack(msg); - if (bl.size < this._mtu) { - this._handleSend(['no', bl]); - return; - } - - this._outgoing[this._count] = { - ack: 0, - chunks: this._chunk(bl) - }; - - if (util.debug) { - this._outgoing[this._count].timer = new Date(); - } - - // Send prelim window. - this._sendWindowedChunks(this._count); - this._count += 1; -}; - -// Set up interval for processing queue. -Reliable.prototype._setupInterval = function() { - // TODO: fail gracefully. - - var self = this; - this._timeout = setInterval(function() { - // FIXME: String stuff makes things terribly async. - var msg = self._queue.shift(); - if (msg._multiple) { - for (var i = 0, ii = msg.length; i < ii; i += 1) { - self._intervalSend(msg[i]); - } - } else { - self._intervalSend(msg); - } - }, this._interval); -}; - -Reliable.prototype._intervalSend = function(msg) { - var self = this; - msg = util.pack(msg); - util.blobToBinaryString(msg, function(str) { - self._dc.send(str); - }); - if (self._queue.length === 0) { - clearTimeout(self._timeout); - self._timeout = null; - //self._processAcks(); - } -}; - -// Go through ACKs to send missing pieces. -Reliable.prototype._processAcks = function() { - for (var id in this._outgoing) { - if (this._outgoing.hasOwnProperty(id)) { - this._sendWindowedChunks(id); - } - } -}; - -// Handle sending a message. -// FIXME: Don't wait for interval time for all messages... -Reliable.prototype._handleSend = function(msg) { - var push = true; - for (var i = 0, ii = this._queue.length; i < ii; i += 1) { - var item = this._queue[i]; - if (item === msg) { - push = false; - } else if (item._multiple && item.indexOf(msg) !== -1) { - push = false; - } - } - if (push) { - this._queue.push(msg); - if (!this._timeout) { - this._setupInterval(); - } - } -}; - -// Set up DataChannel handlers. -Reliable.prototype._setupDC = function() { - // Handle various message types. - var self = this; - this._dc.onmessage = function(e) { - var msg = e.data; - var datatype = msg.constructor; - // FIXME: msg is String until binary is supported. - // Once that happens, this will have to be smarter. - if (datatype === String) { - var ab = util.binaryStringToArrayBuffer(msg); - msg = util.unpack(ab); - self._handleMessage(msg); - } - }; -}; - -// Handles an incoming message. -Reliable.prototype._handleMessage = function(msg) { - var id = msg[1]; - var idata = this._incoming[id]; - var odata = this._outgoing[id]; - var data; - switch (msg[0]) { - // No chunking was done. - case 'no': - var message = id; - if (!!message) { - this.onmessage(util.unpack(message)); - } - break; - // Reached the end of the message. - case 'end': - data = idata; - - // In case end comes first. - this._received[id] = msg[2]; - - if (!data) { - break; - } - - this._ack(id); - break; - case 'ack': - data = odata; - if (!!data) { - var ack = msg[2]; - // Take the larger ACK, for out of order messages. - data.ack = Math.max(ack, data.ack); - - // Clean up when all chunks are ACKed. - if (data.ack >= data.chunks.length) { - util.log('Time: ', new Date() - data.timer); - delete this._outgoing[id]; - } else { - this._processAcks(); - } - } - // If !data, just ignore. - break; - // Received a chunk of data. - case 'chunk': - // Create a new entry if none exists. - data = idata; - if (!data) { - var end = this._received[id]; - if (end === true) { - break; - } - data = { - ack: ['ack', id, 0], - chunks: [] - }; - this._incoming[id] = data; - } - - var n = msg[2]; - var chunk = msg[3]; - data.chunks[n] = new Uint8Array(chunk); - - // If we get the chunk we're looking for, ACK for next missing. - // Otherwise, ACK the same N again. - if (n === data.ack[2]) { - this._calculateNextAck(id); - } - this._ack(id); - break; - default: - // Shouldn't happen, but would make sense for message to just go - // through as is. - this._handleSend(msg); - break; - } -}; - -// Chunks BL into smaller messages. -Reliable.prototype._chunk = function(bl) { - var chunks = []; - var size = bl.size; - var start = 0; - while (start < size) { - var end = Math.min(size, start + this._mtu); - var b = bl.slice(start, end); - var chunk = { - payload: b - } - chunks.push(chunk); - start = end; - } - util.log('Created', chunks.length, 'chunks.'); - return chunks; -}; - -// Sends ACK N, expecting Nth blob chunk for message ID. -Reliable.prototype._ack = function(id) { - var ack = this._incoming[id].ack; - - // if ack is the end value, then call _complete. - if (this._received[id] === ack[2]) { - this._complete(id); - this._received[id] = true; - } - - this._handleSend(ack); -}; - -// Calculates the next ACK number, given chunks. -Reliable.prototype._calculateNextAck = function(id) { - var data = this._incoming[id]; - var chunks = data.chunks; - for (var i = 0, ii = chunks.length; i < ii; i += 1) { - // This chunk is missing!!! Better ACK for it. - if (chunks[i] === undefined) { - data.ack[2] = i; - return; - } - } - data.ack[2] = chunks.length; -}; - -// Sends the next window of chunks. -Reliable.prototype._sendWindowedChunks = function(id) { - util.log('sendWindowedChunks for: ', id); - var data = this._outgoing[id]; - var ch = data.chunks; - var chunks = []; - var limit = Math.min(data.ack + this._window, ch.length); - for (var i = data.ack; i < limit; i += 1) { - if (!ch[i].sent || i === data.ack) { - ch[i].sent = true; - chunks.push(['chunk', id, i, ch[i].payload]); - } - } - if (data.ack + this._window >= ch.length) { - chunks.push(['end', id, ch.length]) - } - chunks._multiple = true; - this._handleSend(chunks); -}; - -// Puts together a message from chunks. -Reliable.prototype._complete = function(id) { - util.log('Completed called for', id); - var self = this; - var chunks = this._incoming[id].chunks; - var bl = new Blob(chunks); - util.blobToArrayBuffer(bl, function(ab) { - self.onmessage(util.unpack(ab)); - }); - delete this._incoming[id]; -}; - -// Ups bandwidth limit on SDP. Meant to be called during offer/answer. -Reliable.higherBandwidthSDP = function(sdp) { - // AS stands for Application-Specific Maximum. - // Bandwidth number is in kilobits / sec. - // See RFC for more info: http://www.ietf.org/rfc/rfc2327.txt - - // Chrome 31+ doesn't want us munging the SDP, so we'll let them have their - // way. - var version = navigator.appVersion.match(/Chrome\/(.*?) /); - if (version) { - version = parseInt(version[1].split('.').shift()); - if (version < 31) { - var parts = sdp.split('b=AS:30'); - var replace = 'b=AS:102400'; // 100 Mbps - if (parts.length > 1) { - return parts[0] + replace + parts[1]; - } - } - } - - return sdp; -}; - -// Overwritten, typically. -Reliable.prototype.onmessage = function(msg) {}; - -module.exports.Reliable = Reliable; - -},{"./util":13}],13:[function(require,module,exports){ -var BinaryPack = require('js-binarypack'); - -var util = { - debug: false, - - inherits: function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }, - extend: function(dest, source) { - for(var key in source) { - if(source.hasOwnProperty(key)) { - dest[key] = source[key]; - } - } - return dest; - }, - pack: BinaryPack.pack, - unpack: BinaryPack.unpack, - - log: function () { - if (util.debug) { - var copy = []; - for (var i = 0; i < arguments.length; i++) { - copy[i] = arguments[i]; - } - copy.unshift('Reliable: '); - console.log.apply(console, copy); - } - }, - - setZeroTimeout: (function(global) { - var timeouts = []; - var messageName = 'zero-timeout-message'; - - // Like setTimeout, but only takes a function argument. There's - // no time argument (always zero) and no arguments (you have to - // use a closure). - function setZeroTimeoutPostMessage(fn) { - timeouts.push(fn); - global.postMessage(messageName, '*'); - } - - function handleMessage(event) { - if (event.source == global && event.data == messageName) { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (timeouts.length) { - timeouts.shift()(); - } - } - } - if (global.addEventListener) { - global.addEventListener('message', handleMessage, true); - } else if (global.attachEvent) { - global.attachEvent('onmessage', handleMessage); - } - return setZeroTimeoutPostMessage; - }(this)), - - blobToArrayBuffer: function(blob, cb){ - var fr = new FileReader(); - fr.onload = function(evt) { - cb(evt.target.result); - }; - fr.readAsArrayBuffer(blob); - }, - blobToBinaryString: function(blob, cb){ - var fr = new FileReader(); - fr.onload = function(evt) { - cb(evt.target.result); - }; - fr.readAsBinaryString(blob); - }, - binaryStringToArrayBuffer: function(binary) { - var byteArray = new Uint8Array(binary.length); - for (var i = 0; i < binary.length; i++) { - byteArray[i] = binary.charCodeAt(i) & 0xff; - } - return byteArray.buffer; - }, - randomToken: function () { - return Math.random().toString(36).substr(2); - } -}; - -module.exports = util; - -},{"js-binarypack":10}]},{},[3]); diff --git a/bower_components/connector/bower_components/peerjs/peer.min.js b/bower_components/connector/bower_components/peerjs/peer.min.js deleted file mode 100644 index 0aa06e93..00000000 --- a/bower_components/connector/bower_components/peerjs/peer.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! peerjs build:0.3.14, production. Copyright(c) 2013 Michelle Bu */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gd.chunkedMTU)return void this._sendChunks(e);d.supports.sctp?d.supports.binaryBlob?this._bufferedSend(e):d.blobToArrayBuffer(e,function(a){c._bufferedSend(a)}):d.blobToBinaryString(e,function(a){c._bufferedSend(a)})}else this._bufferedSend(a)},c.prototype._bufferedSend=function(a){(this._buffering||!this._trySend(a))&&(this._buffer.push(a),this.bufferSize=this._buffer.length)},c.prototype._trySend=function(a){try{this._dc.send(a)}catch(b){this._buffering=!0;var c=this;return setTimeout(function(){c._buffering=!1,c._tryBuffer()},100),!1}return!0},c.prototype._tryBuffer=function(){if(0!==this._buffer.length){var a=this._buffer[0];this._trySend(a)&&(this._buffer.shift(),this.bufferSize=this._buffer.length,this._tryBuffer())}},c.prototype._sendChunks=function(a){for(var b=d.chunk(a),c=0,e=b.length;e>c;c+=1){var a=b[c];this.send(a,!0)}},c.prototype.handleMessage=function(a){var b=a.payload;switch(a.type){case"ANSWER":this._peerBrowser=b.browser,f.handleSDP(a.type,this,b.sdp);break;case"CANDIDATE":f.handleCandidate(this,b.candidate);break;default:d.warn("Unrecognized message type:",a.type,"from peer:",this.peer)}},b.exports=c},{"./negotiator":5,"./util":8,eventemitter3:9,reliable:12}],3:[function(a){window.Socket=a("./socket"),window.MediaConnection=a("./mediaconnection"),window.DataConnection=a("./dataconnection"),window.Peer=a("./peer"),window.RTCPeerConnection=a("./adapter").RTCPeerConnection,window.RTCSessionDescription=a("./adapter").RTCSessionDescription,window.RTCIceCandidate=a("./adapter").RTCIceCandidate,window.Negotiator=a("./negotiator"),window.util=a("./util"),window.BinaryPack=a("js-binarypack")},{"./adapter":1,"./dataconnection":2,"./mediaconnection":4,"./negotiator":5,"./peer":6,"./socket":7,"./util":8,"js-binarypack":10}],4:[function(a,b){function c(a,b,g){return this instanceof c?(e.call(this),this.options=d.extend({},g),this.open=!1,this.type="media",this.peer=a,this.provider=b,this.metadata=this.options.metadata,this.localStream=this.options._stream,this.id=this.options.connectionId||c._idPrefix+d.randomToken(),void(this.localStream&&f.startConnection(this,{_stream:this.localStream,originator:!0}))):new c(a,b,g)}var d=a("./util"),e=a("eventemitter3"),f=a("./negotiator");d.inherits(c,e),c._idPrefix="mc_",c.prototype.addStream=function(a){d.log("Receiving stream",a),this.remoteStream=a,this.emit("stream",a)},c.prototype.handleMessage=function(a){var b=a.payload;switch(a.type){case"ANSWER":f.handleSDP(a.type,this,b.sdp),this.open=!0;break;case"CANDIDATE":f.handleCandidate(this,b.candidate);break;default:d.warn("Unrecognized message type:",a.type,"from peer:",this.peer)}},c.prototype.answer=function(a){if(this.localStream)return void d.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");this.options._payload._stream=a,this.localStream=a,f.startConnection(this,this.options._payload);for(var b=this.provider._getMessages(this.id),c=0,e=b.length;e>c;c+=1)this.handleMessage(b[c]);this.open=!0},c.prototype.close=function(){this.open&&(this.open=!1,f.cleanup(this),this.emit("close"))},b.exports=c},{"./negotiator":5,"./util":8,eventemitter3:9}],5:[function(a,b){var c=a("./util"),d=a("./adapter").RTCPeerConnection,e=a("./adapter").RTCSessionDescription,f=a("./adapter").RTCIceCandidate,g={pcs:{data:{},media:{}},queue:[]};g._idPrefix="pc_",g.startConnection=function(a,b){var d=g._getPeerConnection(a,b);if("media"===a.type&&b._stream&&d.addStream(b._stream),a.pc=a.peerConnection=d,b.originator){if("data"===a.type){var e={};c.supports.sctp||(e={reliable:b.reliable});var f=d.createDataChannel(a.label,e);a.initialize(f)}c.supports.onnegotiationneeded||g._makeOffer(a)}else g.handleSDP("OFFER",a,b.sdp)},g._getPeerConnection=function(a,b){g.pcs[a.type]||c.error(a.type+" is not a valid connection type. Maybe you overrode the `type` property somewhere."),g.pcs[a.type][a.peer]||(g.pcs[a.type][a.peer]={});{var d;g.pcs[a.type][a.peer]}return b.pc&&(d=g.pcs[a.type][a.peer][b.pc]),d&&"stable"===d.signalingState||(d=g._startPeerConnection(a)),d},g._startPeerConnection=function(a){c.log("Creating RTCPeerConnection.");var b=g._idPrefix+c.randomToken(),e={};"data"!==a.type||c.supports.sctp?"media"===a.type&&(e={optional:[{DtlsSrtpKeyAgreement:!0}]}):e={optional:[{RtpDataChannels:!0}]};var f=new d(a.provider.options.config,e);return g.pcs[a.type][a.peer][b]=f,g._setupListeners(a,f,b),f},g._setupListeners=function(a,b){var d=a.peer,e=a.id,f=a.provider;c.log("Listening for ICE candidates."),b.onicecandidate=function(b){b.candidate&&(c.log("Received ICE candidates for:",a.peer),f.socket.send({type:"CANDIDATE",payload:{candidate:b.candidate,type:a.type,connectionId:a.id},dst:d}))},b.oniceconnectionstatechange=function(){switch(b.iceConnectionState){case"disconnected":case"failed":c.log("iceConnectionState is disconnected, closing connections to "+d),a.close();break;case"completed":b.onicecandidate=c.noop}},b.onicechange=b.oniceconnectionstatechange,c.log("Listening for `negotiationneeded`"),b.onnegotiationneeded=function(){c.log("`negotiationneeded` triggered"),"stable"==b.signalingState?g._makeOffer(a):c.log("onnegotiationneeded triggered when not stable. Is another connection being established?")},c.log("Listening for data channel"),b.ondatachannel=function(a){c.log("Received data channel");var b=a.channel,g=f.getConnection(d,e);g.initialize(b)},c.log("Listening for remote stream"),b.onaddstream=function(a){c.log("Received remote stream");var b=a.stream,g=f.getConnection(d,e);"media"===g.type&&g.addStream(b)}},g.cleanup=function(a){c.log("Cleaning up PeerConnection to "+a.peer);var b=a.pc;!b||"closed"===b.readyState&&"closed"===b.signalingState||(b.close(),a.pc=null)},g._makeOffer=function(a){var b=a.pc;b.createOffer(function(d){c.log("Created offer."),!c.supports.sctp&&"data"===a.type&&a.reliable&&(d.sdp=Reliable.higherBandwidthSDP(d.sdp)),b.setLocalDescription(d,function(){c.log("Set localDescription: offer","for:",a.peer),a.provider.socket.send({type:"OFFER",payload:{sdp:d,type:a.type,label:a.label,connectionId:a.id,reliable:a.reliable,serialization:a.serialization,metadata:a.metadata,browser:c.browser},dst:a.peer})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to setLocalDescription, ",b)})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to createOffer, ",b)},a.options.constraints)},g._makeAnswer=function(a){var b=a.pc;b.createAnswer(function(d){c.log("Created answer."),!c.supports.sctp&&"data"===a.type&&a.reliable&&(d.sdp=Reliable.higherBandwidthSDP(d.sdp)),b.setLocalDescription(d,function(){c.log("Set localDescription: answer","for:",a.peer),a.provider.socket.send({type:"ANSWER",payload:{sdp:d,type:a.type,connectionId:a.id,browser:c.browser},dst:a.peer})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to setLocalDescription, ",b)})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to create answer, ",b)})},g.handleSDP=function(a,b,d){d=new e(d);var f=b.pc;c.log("Setting remote description",d),f.setRemoteDescription(d,function(){c.log("Set remoteDescription:",a,"for:",b.peer),"OFFER"===a&&g._makeAnswer(b)},function(a){b.provider.emitError("webrtc",a),c.log("Failed to setRemoteDescription, ",a)})},g.handleCandidate=function(a,b){var d=b.candidate,e=b.sdpMLineIndex;a.pc.addIceCandidate(new f({sdpMLineIndex:e,candidate:d})),c.log("Added ICE candidate for:",a.peer)},b.exports=g},{"./adapter":1,"./util":8}],6:[function(a,b){function c(a,b){return this instanceof c?(e.call(this),a&&a.constructor==Object?(b=a,a=void 0):a&&(a=a.toString()),b=d.extend({debug:0,host:d.CLOUD_HOST,port:d.CLOUD_PORT,key:"peerjs",path:"/",token:d.randomToken(),config:d.defaultConfig},b),this.options=b,"/"===b.host&&(b.host=window.location.hostname),"/"!==b.path[0]&&(b.path="/"+b.path),"/"!==b.path[b.path.length-1]&&(b.path+="/"),void 0===b.secure&&b.host!==d.CLOUD_HOST&&(b.secure=d.isSecure()),b.logFunction&&d.setLogFunction(b.logFunction),d.setLogLevel(b.debug),d.supports.audioVideo||d.supports.data?d.validateId(a)?d.validateKey(b.key)?b.secure&&"0.peerjs.com"===b.host?void this._delayedAbort("ssl-unavailable","The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS."):(this.destroyed=!1,this.disconnected=!1,this.open=!1,this.connections={},this._lostMessages={},this._initializeServerConnection(),void(a?this._initialize(a):this._retrieveId())):void this._delayedAbort("invalid-key",'API KEY "'+b.key+'" is invalid'):void this._delayedAbort("invalid-id",'ID "'+a+'" is invalid'):void this._delayedAbort("browser-incompatible","The current browser does not support WebRTC")):new c(a,b)}var d=a("./util"),e=a("eventemitter3"),f=a("./socket"),g=a("./mediaconnection"),h=a("./dataconnection");d.inherits(c,e),c.prototype._initializeServerConnection=function(){var a=this;this.socket=new f(this.options.secure,this.options.host,this.options.port,this.options.path,this.options.key),this.socket.on("message",function(b){a._handleMessage(b)}),this.socket.on("error",function(b){a._abort("socket-error",b)}),this.socket.on("disconnected",function(){a.disconnected||(a.emitError("network","Lost connection to server."),a.disconnect())}),this.socket.on("close",function(){a.disconnected||a._abort("socket-closed","Underlying socket is already closed.")})},c.prototype._retrieveId=function(){var a=this,b=new XMLHttpRequest,c=this.options.secure?"https://":"http://",e=c+this.options.host+":"+this.options.port+this.options.path+this.options.key+"/id",f="?ts="+(new Date).getTime()+Math.random();e+=f,b.open("get",e,!0),b.onerror=function(b){d.error("Error retrieving ID",b);var c="";"/"===a.options.path&&a.options.host!==d.CLOUD_HOST&&(c=" If you passed in a `path` to your self-hosted PeerServer, you'll also need to pass in that same path when creating a new Peer."),a._abort("server-error","Could not get an ID from the server."+c)},b.onreadystatechange=function(){return 4===b.readyState?200!==b.status?void b.onerror():void a._initialize(b.responseText):void 0},b.send(null)},c.prototype._initialize=function(a){this.id=a,this.socket.start(this.id,this.options.token)},c.prototype._handleMessage=function(a){var b,c=a.type,e=a.payload,f=a.src;switch(c){case"OPEN":this.emit("open",this.id),this.open=!0;break;case"ERROR":this._abort("server-error",e.msg);break;case"ID-TAKEN":this._abort("unavailable-id","ID `"+this.id+"` is taken");break;case"INVALID-KEY":this._abort("invalid-key",'API KEY "'+this.options.key+'" is invalid');break;case"LEAVE":d.log("Received leave message from",f),this._cleanupPeer(f);break;case"EXPIRE":this.emitError("peer-unavailable","Could not connect to peer "+f);break;case"OFFER":var i=e.connectionId;if(b=this.getConnection(f,i))d.warn("Offer received for existing Connection ID:",i);else{if("media"===e.type)b=new g(f,this,{connectionId:i,_payload:e,metadata:e.metadata}),this._addConnection(f,b),this.emit("call",b);else{if("data"!==e.type)return void d.warn("Received malformed connection type:",e.type);b=new h(f,this,{connectionId:i,_payload:e,metadata:e.metadata,label:e.label,serialization:e.serialization,reliable:e.reliable}),this._addConnection(f,b),this.emit("connection",b)}for(var j=this._getMessages(i),k=0,l=j.length;l>k;k+=1)b.handleMessage(j[k])}break;default:if(!e)return void d.warn("You received a malformed message from "+f+" of type "+c);var m=e.connectionId;b=this.getConnection(f,m),b&&b.pc?b.handleMessage(a):m?this._storeMessage(m,a):d.warn("You received an unrecognized message:",a)}},c.prototype._storeMessage=function(a,b){this._lostMessages[a]||(this._lostMessages[a]=[]),this._lostMessages[a].push(b)},c.prototype._getMessages=function(a){var b=this._lostMessages[a];return b?(delete this._lostMessages[a],b):[]},c.prototype.connect=function(a,b){if(this.disconnected)return d.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect, or call reconnect on this peer if you believe its ID to still be available."),void this.emitError("disconnected","Cannot connect to new Peer after disconnecting from server.");var c=new h(a,this,b);return this._addConnection(a,c),c},c.prototype.call=function(a,b,c){if(this.disconnected)return d.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect."),void this.emitError("disconnected","Cannot connect to new Peer after disconnecting from server.");if(!b)return void d.error("To call a peer, you must provide a stream from your browser's `getUserMedia`.");c=c||{},c._stream=b;var e=new g(a,this,c);return this._addConnection(a,e),e},c.prototype._addConnection=function(a,b){this.connections[a]||(this.connections[a]=[]),this.connections[a].push(b)},c.prototype.getConnection=function(a,b){var c=this.connections[a];if(!c)return null;for(var d=0,e=c.length;e>d;d++)if(c[d].id===b)return c[d];return null},c.prototype._delayedAbort=function(a,b){var c=this;d.setZeroTimeout(function(){c._abort(a,b)})},c.prototype._abort=function(a,b){d.error("Aborting!"),this._lastServerId?this.disconnect():this.destroy(),this.emitError(a,b)},c.prototype.emitError=function(a,b){d.error("Error:",b),"string"==typeof b&&(b=new Error(b)),b.type=a,this.emit("error",b)},c.prototype.destroy=function(){this.destroyed||(this._cleanup(),this.disconnect(),this.destroyed=!0)},c.prototype._cleanup=function(){if(this.connections)for(var a=Object.keys(this.connections),b=0,c=a.length;c>b;b++)this._cleanupPeer(a[b]);this.emit("close")},c.prototype._cleanupPeer=function(a){for(var b=this.connections[a],c=0,d=b.length;d>c;c+=1)b[c].close()},c.prototype.disconnect=function(){var a=this;d.setZeroTimeout(function(){a.disconnected||(a.disconnected=!0,a.open=!1,a.socket&&a.socket.close(),a.emit("disconnected",a.id),a._lastServerId=a.id,a.id=null)})},c.prototype.reconnect=function(){if(this.disconnected&&!this.destroyed)d.log("Attempting reconnection to server with ID "+this._lastServerId),this.disconnected=!1,this._initializeServerConnection(),this._initialize(this._lastServerId);else{if(this.destroyed)throw new Error("This peer cannot reconnect to the server. It has already been destroyed.");if(this.disconnected||this.open)throw new Error("Peer "+this.id+" cannot reconnect because it is not disconnected from the server!");d.error("In a hurry? We're still trying to make the initial connection!")}},c.prototype.listAllPeers=function(a){a=a||function(){};var b=this,c=new XMLHttpRequest,e=this.options.secure?"https://":"http://",f=e+this.options.host+":"+this.options.port+this.options.path+this.options.key+"/peers",g="?ts="+(new Date).getTime()+Math.random();f+=g,c.open("get",f,!0),c.onerror=function(){b._abort("server-error","Could not get peers from the server."),a([])},c.onreadystatechange=function(){if(4===c.readyState){if(401===c.status){var e="";throw e=b.options.host!==d.CLOUD_HOST?"It looks like you're using the cloud server. You can email team@peerjs.com to enable peer listing for your API key.":"You need to enable `allow_discovery` on your self-hosted PeerServer to use this feature.",a([]),new Error("It doesn't look like you have permission to list peers IDs. "+e)}a(200!==c.status?[]:JSON.parse(c.responseText))}},c.send(null)},b.exports=c},{"./dataconnection":2,"./mediaconnection":4,"./socket":7,"./util":8,eventemitter3:9}],7:[function(a,b){function c(a,b,d,f,g){if(!(this instanceof c))return new c(a,b,d,f,g);e.call(this),this.disconnected=!1,this._queue=[];var h=a?"https://":"http://",i=a?"wss://":"ws://";this._httpUrl=h+b+":"+d+f+g,this._wsUrl=i+b+":"+d+f+"peerjs?key="+g}var d=a("./util"),e=a("eventemitter3");d.inherits(c,e),c.prototype.start=function(a,b){this.id=a,this._httpUrl+="/"+a+"/"+b,this._wsUrl+="&id="+a+"&token="+b,this._startXhrStream(),this._startWebSocket()},c.prototype._startWebSocket=function(){var a=this;this._socket||(this._socket=new WebSocket(this._wsUrl),this._socket.onmessage=function(b){try{var c=JSON.parse(b.data)}catch(e){return void d.log("Invalid server message",b.data)}a.emit("message",c)},this._socket.onclose=function(){d.log("Socket closed."),a.disconnected=!0,a.emit("disconnected")},this._socket.onopen=function(){a._timeout&&(clearTimeout(a._timeout),setTimeout(function(){a._http.abort(),a._http=null},5e3)),a._sendQueuedMessages(),d.log("Socket open")})},c.prototype._startXhrStream=function(a){try{var b=this;this._http=new XMLHttpRequest,this._http._index=1,this._http._streamIndex=a||0,this._http.open("post",this._httpUrl+"/id?i="+this._http._streamIndex,!0),this._http.onerror=function(){clearTimeout(b._timeout),b.emit("disconnected")},this._http.onreadystatechange=function(){2==this.readyState&&this.old?(this.old.abort(),delete this.old):this.readyState>2&&200===this.status&&this.responseText&&b._handleStream(this)},this._http.send(null),this._setHTTPTimeout()}catch(c){d.log("XMLHttpRequest not available; defaulting to WebSockets")}},c.prototype._handleStream=function(a){var b=a.responseText.split("\n");if(a._buffer)for(;a._buffer.length>0;){var c=a._buffer.shift(),e=b[c];try{e=JSON.parse(e)}catch(f){a._buffer.shift(c);break}this.emit("message",e)}var g=b[a._index];if(g)if(a._index+=1,a._index===b.length)a._buffer||(a._buffer=[]),a._buffer.push(a._index-1);else{try{g=JSON.parse(g)}catch(f){return void d.log("Invalid server message",g)}this.emit("message",g)}},c.prototype._setHTTPTimeout=function(){var a=this;this._timeout=setTimeout(function(){var b=a._http;a._wsOpen()?b.abort():(a._startXhrStream(b._streamIndex+1),a._http.old=b)},25e3)},c.prototype._wsOpen=function(){return this._socket&&1==this._socket.readyState},c.prototype._sendQueuedMessages=function(){for(var a=0,b=this._queue.length;b>a;a+=1)this.send(this._queue[a])},c.prototype.send=function(a){if(!this.disconnected){if(!this.id)return void this._queue.push(a);if(!a.type)return void this.emit("error","Invalid message");var b=JSON.stringify(a);if(this._wsOpen())this._socket.send(b);else{var c=new XMLHttpRequest,d=this._httpUrl+"/"+a.type.toLowerCase();c.open("post",d,!0),c.setRequestHeader("Content-Type","application/json"),c.send(b)}}},c.prototype.close=function(){!this.disconnected&&this._wsOpen()&&(this._socket.close(),this.disconnected=!0)},b.exports=c},{"./util":8,eventemitter3:9}],8:[function(a,b){var c={iceServers:[{url:"stun:stun.l.google.com:19302"}]},d=1,e=a("js-binarypack"),f=a("./adapter").RTCPeerConnection,g={noop:function(){},CLOUD_HOST:"0.peerjs.com",CLOUD_PORT:9e3,chunkedBrowsers:{Chrome:1},chunkedMTU:16300,logLevel:0,setLogLevel:function(a){var b=parseInt(a,10);g.logLevel=isNaN(parseInt(a,10))?a?3:0:b,g.log=g.warn=g.error=g.noop,g.logLevel>0&&(g.error=g._printWith("ERROR")),g.logLevel>1&&(g.warn=g._printWith("WARNING")),g.logLevel>2&&(g.log=g._print)},setLogFunction:function(a){a.constructor!==Function?g.warn("The log function you passed in is not a function. Defaulting to regular logs."):g._print=a},_printWith:function(a){return function(){var b=Array.prototype.slice.call(arguments);b.unshift(a),g._print.apply(g,b)}},_print:function(){var a=!1,b=Array.prototype.slice.call(arguments);b.unshift("PeerJS: ");for(var c=0,d=b.length;d>c;c++)b[c]instanceof Error&&(b[c]="("+b[c].name+") "+b[c].message,a=!0);a?console.error.apply(console,b):console.log.apply(console,b)},defaultConfig:c,browser:function(){return window.mozRTCPeerConnection?"Firefox":window.webkitRTCPeerConnection?"Chrome":window.RTCPeerConnection?"Supported":"Unsupported"}(),supports:function(){if("undefined"==typeof f)return{};var a,b,d=!0,e=!0,h=!1,i=!1,j=!!window.webkitRTCPeerConnection;try{a=new f(c,{optional:[{RtpDataChannels:!0}]})}catch(k){d=!1,e=!1}if(d)try{b=a.createDataChannel("_PEERJSTEST")}catch(k){d=!1}if(d){try{b.binaryType="blob",h=!0}catch(k){}var l=new f(c,{});try{var m=l.createDataChannel("_PEERJSRELIABLETEST",{});i=m.reliable}catch(k){}l.close()}if(e&&(e=!!a.addStream),!j&&d){var n=new f(c,{optional:[{RtpDataChannels:!0}]});n.onnegotiationneeded=function(){j=!0,g&&g.supports&&(g.supports.onnegotiationneeded=!0)},n.createDataChannel("_PEERJSNEGOTIATIONTEST"),setTimeout(function(){n.close()},1e3)}return a&&a.close(),{audioVideo:e,data:d,binaryBlob:h,binary:i,reliable:i,sctp:i,onnegotiationneeded:j}}(),validateId:function(a){return!a||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(a)},validateKey:function(a){return!a||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(a)},debug:!1,inherits:function(a,b){a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})},extend:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},pack:e.pack,unpack:e.unpack,log:function(){if(g.debug){var a=!1,b=Array.prototype.slice.call(arguments);b.unshift("PeerJS: ");for(var c=0,d=b.length;d>c;c++)b[c]instanceof Error&&(b[c]="("+b[c].name+") "+b[c].message,a=!0);a?console.error.apply(console,b):console.log.apply(console,b)}},setZeroTimeout:function(a){function b(b){d.push(b),a.postMessage(e,"*")}function c(b){b.source==a&&b.data==e&&(b.stopPropagation&&b.stopPropagation(),d.length&&d.shift()())}var d=[],e="zero-timeout-message";return a.addEventListener?a.addEventListener("message",c,!0):a.attachEvent&&a.attachEvent("onmessage",c),b}(window),chunk:function(a){for(var b=[],c=a.size,e=index=0,f=Math.ceil(c/g.chunkedMTU);c>e;){var h=Math.min(c,e+g.chunkedMTU),i=a.slice(e,h),j={__peerData:d,n:index,data:i,total:f};b.push(j),e=h,index+=1}return d+=1,b},blobToArrayBuffer:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsArrayBuffer(a)},blobToBinaryString:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsBinaryString(a)},binaryStringToArrayBuffer:function(a){for(var b=new Uint8Array(a.length),c=0;cb;b++)d.push(this._events[a][b].fn);return d},d.prototype.emit=function(a,b,c,d,e,f){if(!this._events||!this._events[a])return!1;var g,h,i,j=this._events[a],k=j.length,l=arguments.length,m=j[0];if(1===k){switch(m.once&&this.removeListener(a,m.fn,!0),l){case 1:return m.fn.call(m.context),!0;case 2:return m.fn.call(m.context,b),!0;case 3:return m.fn.call(m.context,b,c),!0;case 4:return m.fn.call(m.context,b,c,d),!0;case 5:return m.fn.call(m.context,b,c,d,e),!0;case 6:return m.fn.call(m.context,b,c,d,e,f),!0}for(h=1,g=new Array(l-1);l>h;h++)g[h-1]=arguments[h];m.fn.apply(m.context,g)}else for(h=0;k>h;h++)switch(j[h].once&&this.removeListener(a,j[h].fn,!0),l){case 1:j[h].fn.call(j[h].context);break;case 2:j[h].fn.call(j[h].context,b);break;case 3:j[h].fn.call(j[h].context,b,c);break;default:if(!g)for(i=1,g=new Array(l-1);l>i;i++)g[i-1]=arguments[i];j[h].fn.apply(j[h].context,g)}return!0},d.prototype.on=function(a,b,d){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),this._events[a].push(new c(b,d||this)),this},d.prototype.once=function(a,b,d){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),this._events[a].push(new c(b,d||this,!0)),this},d.prototype.removeListener=function(a,b,c){if(!this._events||!this._events[a])return this;var d=this._events[a],e=[];if(b)for(var f=0,g=d.length;g>f;f++)d[f].fn!==b&&d[f].once!==c&&e.push(d[f]);return this._events[a]=e.length?e:null,this},d.prototype.removeAllListeners=function(a){return this._events?(a?this._events[a]=null:this._events={},this):this},d.prototype.off=d.prototype.removeListener,d.prototype.addListener=d.prototype.on,d.prototype.setMaxListeners=function(){return this},d.EventEmitter=d,d.EventEmitter2=d,d.EventEmitter3=d,"object"==typeof b&&b.exports&&(b.exports=d)},{}],10:[function(a,b){function c(a){this.index=0,this.dataBuffer=a,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}function d(){this.bufferBuilder=new g}function e(a){var b=a.charCodeAt(0);return 2047>=b?"00":65535>=b?"000":2097151>=b?"0000":67108863>=b?"00000":"000000"}function f(a){return a.length>600?new Blob([a]).size:a.replace(/[^\u0000-\u007F]/g,e).length}var g=a("./bufferbuilder").BufferBuilder,h=a("./bufferbuilder").binaryFeatures,i={unpack:function(a){var b=new c(a);return b.unpack()},pack:function(a){var b=new d;b.pack(a);var c=b.getBuffer();return c}};b.exports=i,c.prototype.unpack=function(){var a=this.unpack_uint8();if(128>a){var b=a;return b}if(32>(224^a)){var c=(224^a)-32;return c}var d;if((d=160^a)<=15)return this.unpack_raw(d);if((d=176^a)<=15)return this.unpack_string(d);if((d=144^a)<=15)return this.unpack_array(d);if((d=128^a)<=15)return this.unpack_map(d);switch(a){case 192:return null;case 193:return void 0;case 194:return!1;case 195:return!0;case 202:return this.unpack_float();case 203:return this.unpack_double();case 204:return this.unpack_uint8();case 205:return this.unpack_uint16();case 206:return this.unpack_uint32();case 207:return this.unpack_uint64();case 208:return this.unpack_int8();case 209:return this.unpack_int16();case 210:return this.unpack_int32();case 211:return this.unpack_int64();case 212:return void 0;case 213:return void 0;case 214:return void 0;case 215:return void 0;case 216:return d=this.unpack_uint16(),this.unpack_string(d);case 217:return d=this.unpack_uint32(),this.unpack_string(d);case 218:return d=this.unpack_uint16(),this.unpack_raw(d);case 219:return d=this.unpack_uint32(),this.unpack_raw(d);case 220:return d=this.unpack_uint16(),this.unpack_array(d);case 221:return d=this.unpack_uint32(),this.unpack_array(d);case 222:return d=this.unpack_uint16(),this.unpack_map(d);case 223:return d=this.unpack_uint32(),this.unpack_map(d)}},c.prototype.unpack_uint8=function(){var a=255&this.dataView[this.index];return this.index++,a},c.prototype.unpack_uint16=function(){var a=this.read(2),b=256*(255&a[0])+(255&a[1]);return this.index+=2,b},c.prototype.unpack_uint32=function(){var a=this.read(4),b=256*(256*(256*a[0]+a[1])+a[2])+a[3];return this.index+=4,b},c.prototype.unpack_uint64=function(){var a=this.read(8),b=256*(256*(256*(256*(256*(256*(256*a[0]+a[1])+a[2])+a[3])+a[4])+a[5])+a[6])+a[7];return this.index+=8,b},c.prototype.unpack_int8=function(){var a=this.unpack_uint8();return 128>a?a:a-256},c.prototype.unpack_int16=function(){var a=this.unpack_uint16();return 32768>a?a:a-65536},c.prototype.unpack_int32=function(){var a=this.unpack_uint32();return ae;)b=d[e],128>b?(f+=String.fromCharCode(b),e++):32>(192^b)?(c=(192^b)<<6|63&d[e+1],f+=String.fromCharCode(c),e+=2):(c=(15&b)<<12|(63&d[e+1])<<6|63&d[e+2],f+=String.fromCharCode(c),e+=3);return this.index+=a,f},c.prototype.unpack_array=function(a){for(var b=new Array(a),c=0;a>c;c++)b[c]=this.unpack();return b},c.prototype.unpack_map=function(a){for(var b={},c=0;a>c;c++){var d=this.unpack(),e=this.unpack();b[d]=e}return b},c.prototype.unpack_float=function(){var a=this.unpack_uint32(),b=a>>31,c=(a>>23&255)-127,d=8388607&a|8388608;return(0==b?1:-1)*d*Math.pow(2,c-23)},c.prototype.unpack_double=function(){var a=this.unpack_uint32(),b=this.unpack_uint32(),c=a>>31,d=(a>>20&2047)-1023,e=1048575&a|1048576,f=e*Math.pow(2,d-20)+b*Math.pow(2,d-52);return(0==c?1:-1)*f},c.prototype.read=function(a){var b=this.index;if(b+a<=this.length)return this.dataView.subarray(b,b+a);throw new Error("BinaryPackFailure: read index out of range")},d.prototype.getBuffer=function(){return this.bufferBuilder.getBuffer()},d.prototype.pack=function(a){var b=typeof a;if("string"==b)this.pack_string(a);else if("number"==b)Math.floor(a)===a?this.pack_integer(a):this.pack_double(a);else if("boolean"==b)a===!0?this.bufferBuilder.append(195):a===!1&&this.bufferBuilder.append(194);else if("undefined"==b)this.bufferBuilder.append(192);else{if("object"!=b)throw new Error('Type "'+b+'" not yet supported');if(null===a)this.bufferBuilder.append(192);else{var c=a.constructor;if(c==Array)this.pack_array(a);else if(c==Blob||c==File)this.pack_bin(a); -else if(c==ArrayBuffer)this.pack_bin(h.useArrayBufferView?new Uint8Array(a):a);else if("BYTES_PER_ELEMENT"in a)this.pack_bin(h.useArrayBufferView?new Uint8Array(a.buffer):a.buffer);else if(c==Object)this.pack_object(a);else if(c==Date)this.pack_string(a.toString());else{if("function"!=typeof a.toBinaryPack)throw new Error('Type "'+c.toString()+'" not yet supported');this.bufferBuilder.append(a.toBinaryPack())}}}this.bufferBuilder.flush()},d.prototype.pack_bin=function(a){var b=a.length||a.byteLength||a.size;if(15>=b)this.pack_uint8(160+b);else if(65535>=b)this.bufferBuilder.append(218),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(b)}this.bufferBuilder.append(a)},d.prototype.pack_string=function(a){var b=f(a);if(15>=b)this.pack_uint8(176+b);else if(65535>=b)this.bufferBuilder.append(216),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(b)}this.bufferBuilder.append(a)},d.prototype.pack_array=function(a){var b=a.length;if(15>=b)this.pack_uint8(144+b);else if(65535>=b)this.bufferBuilder.append(220),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(b)}for(var c=0;b>c;c++)this.pack(a[c])},d.prototype.pack_integer=function(a){if(a>=-32&&127>=a)this.bufferBuilder.append(255&a);else if(a>=0&&255>=a)this.bufferBuilder.append(204),this.pack_uint8(a);else if(a>=-128&&127>=a)this.bufferBuilder.append(208),this.pack_int8(a);else if(a>=0&&65535>=a)this.bufferBuilder.append(205),this.pack_uint16(a);else if(a>=-32768&&32767>=a)this.bufferBuilder.append(209),this.pack_int16(a);else if(a>=0&&4294967295>=a)this.bufferBuilder.append(206),this.pack_uint32(a);else if(a>=-2147483648&&2147483647>=a)this.bufferBuilder.append(210),this.pack_int32(a);else if(a>=-0x8000000000000000&&0x8000000000000000>=a)this.bufferBuilder.append(211),this.pack_int64(a);else{if(!(a>=0&&0x10000000000000000>=a))throw new Error("Invalid integer");this.bufferBuilder.append(207),this.pack_uint64(a)}},d.prototype.pack_double=function(a){var b=0;0>a&&(b=1,a=-a);var c=Math.floor(Math.log(a)/Math.LN2),d=a/Math.pow(2,c)-1,e=Math.floor(d*Math.pow(2,52)),f=Math.pow(2,32),g=b<<31|c+1023<<20|e/f&1048575,h=e%f;this.bufferBuilder.append(203),this.pack_int32(g),this.pack_int32(h)},d.prototype.pack_object=function(a){var b=Object.keys(a),c=b.length;if(15>=c)this.pack_uint8(128+c);else if(65535>=c)this.bufferBuilder.append(222),this.pack_uint16(c);else{if(!(4294967295>=c))throw new Error("Invalid length");this.bufferBuilder.append(223),this.pack_uint32(c)}for(var d in a)a.hasOwnProperty(d)&&(this.pack(d),this.pack(a[d]))},d.prototype.pack_uint8=function(a){this.bufferBuilder.append(a)},d.prototype.pack_uint16=function(a){this.bufferBuilder.append(a>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_uint32=function(a){var b=4294967295&a;this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b)},d.prototype.pack_uint64=function(a){var b=a/Math.pow(2,32),c=a%Math.pow(2,32);this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b),this.bufferBuilder.append((4278190080&c)>>>24),this.bufferBuilder.append((16711680&c)>>>16),this.bufferBuilder.append((65280&c)>>>8),this.bufferBuilder.append(255&c)},d.prototype.pack_int8=function(a){this.bufferBuilder.append(255&a)},d.prototype.pack_int16=function(a){this.bufferBuilder.append((65280&a)>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_int32=function(a){this.bufferBuilder.append(a>>>24&255),this.bufferBuilder.append((16711680&a)>>>16),this.bufferBuilder.append((65280&a)>>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_int64=function(a){var b=Math.floor(a/Math.pow(2,32)),c=a%Math.pow(2,32);this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b),this.bufferBuilder.append((4278190080&c)>>>24),this.bufferBuilder.append((16711680&c)>>>16),this.bufferBuilder.append((65280&c)>>>8),this.bufferBuilder.append(255&c)}},{"./bufferbuilder":11}],11:[function(a,b){function c(){this._pieces=[],this._parts=[]}var d={};d.useBlobBuilder=function(){try{return new Blob([]),!1}catch(a){return!0}}(),d.useArrayBufferView=!d.useBlobBuilder&&function(){try{return 0===new Blob([new Uint8Array([])]).size}catch(a){return!0}}(),b.exports.binaryFeatures=d;var e=b.exports.BlobBuilder;"undefined"!=typeof window&&(e=b.exports.BlobBuilder=window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder||window.BlobBuilder),c.prototype.append=function(a){"number"==typeof a?this._pieces.push(a):(this.flush(),this._parts.push(a))},c.prototype.flush=function(){if(this._pieces.length>0){var a=new Uint8Array(this._pieces);d.useArrayBufferView||(a=a.buffer),this._parts.push(a),this._pieces=[]}},c.prototype.getBuffer=function(){if(this.flush(),d.useBlobBuilder){for(var a=new e,b=0,c=this._parts.length;c>b;b++)a.append(this._parts[b]);return a.getBlob()}return new Blob(this._parts)},b.exports.BufferBuilder=c},{}],12:[function(a,b){function c(a,b){return this instanceof c?(this._dc=a,d.debug=b,this._outgoing={},this._incoming={},this._received={},this._window=1e3,this._mtu=500,this._interval=0,this._count=0,this._queue=[],void this._setupDC()):new c(a)}var d=a("./util");c.prototype.send=function(a){var b=d.pack(a);return b.sizec;c+=1)a._intervalSend(b[c]);else a._intervalSend(b)},this._interval)},c.prototype._intervalSend=function(a){var b=this;a=d.pack(a),d.blobToBinaryString(a,function(a){b._dc.send(a)}),0===b._queue.length&&(clearTimeout(b._timeout),b._timeout=null)},c.prototype._processAcks=function(){for(var a in this._outgoing)this._outgoing.hasOwnProperty(a)&&this._sendWindowedChunks(a)},c.prototype._handleSend=function(a){for(var b=!0,c=0,d=this._queue.length;d>c;c+=1){var e=this._queue[c];e===a?b=!1:e._multiple&&-1!==e.indexOf(a)&&(b=!1)}b&&(this._queue.push(a),this._timeout||this._setupInterval())},c.prototype._setupDC=function(){var a=this;this._dc.onmessage=function(b){var c=b.data,e=c.constructor;if(e===String){var f=d.binaryStringToArrayBuffer(c);c=d.unpack(f),a._handleMessage(c)}}},c.prototype._handleMessage=function(a){var b,c=a[1],e=this._incoming[c],f=this._outgoing[c];switch(a[0]){case"no":var g=c;g&&this.onmessage(d.unpack(g));break;case"end":if(b=e,this._received[c]=a[2],!b)break;this._ack(c);break;case"ack":if(b=f){var h=a[2];b.ack=Math.max(h,b.ack),b.ack>=b.chunks.length?(d.log("Time: ",new Date-b.timer),delete this._outgoing[c]):this._processAcks()}break;case"chunk":if(b=e,!b){var i=this._received[c];if(i===!0)break;b={ack:["ack",c,0],chunks:[]},this._incoming[c]=b}var j=a[2],k=a[3];b.chunks[j]=new Uint8Array(k),j===b.ack[2]&&this._calculateNextAck(c),this._ack(c);break;default:this._handleSend(a)}},c.prototype._chunk=function(a){for(var b=[],c=a.size,e=0;c>e;){var f=Math.min(c,e+this._mtu),g=a.slice(e,f),h={payload:g};b.push(h),e=f}return d.log("Created",b.length,"chunks."),b},c.prototype._ack=function(a){var b=this._incoming[a].ack;this._received[a]===b[2]&&(this._complete(a),this._received[a]=!0),this._handleSend(b)},c.prototype._calculateNextAck=function(a){for(var b=this._incoming[a],c=b.chunks,d=0,e=c.length;e>d;d+=1)if(void 0===c[d])return void(b.ack[2]=d);b.ack[2]=c.length},c.prototype._sendWindowedChunks=function(a){d.log("sendWindowedChunks for: ",a);for(var b=this._outgoing[a],c=b.chunks,e=[],f=Math.min(b.ack+this._window,c.length),g=b.ack;f>g;g+=1)c[g].sent&&g!==b.ack||(c[g].sent=!0,e.push(["chunk",a,g,c[g].payload]));b.ack+this._window>=c.length&&e.push(["end",a,c.length]),e._multiple=!0,this._handleSend(e)},c.prototype._complete=function(a){d.log("Completed called for",a);var b=this,c=this._incoming[a].chunks,e=new Blob(c);d.blobToArrayBuffer(e,function(a){b.onmessage(d.unpack(a))}),delete this._incoming[a]},c.higherBandwidthSDP=function(a){var b=navigator.appVersion.match(/Chrome\/(.*?) /);if(b&&(b=parseInt(b[1].split(".").shift()),31>b)){var c=a.split("b=AS:30"),d="b=AS:102400";if(c.length>1)return c[0]+d+c[1]}return a},c.prototype.onmessage=function(){},b.exports.Reliable=c},{"./util":13}],13:[function(a,b){var c=a("js-binarypack"),d={debug:!1,inherits:function(a,b){a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})},extend:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},pack:c.pack,unpack:c.unpack,log:function(){if(d.debug){for(var a=[],b=0;b", - "private": true, - "dependencies": { - "core-component-page": "Polymer/core-component-page#^0.5.0", - "webcomponentsjs": "Polymer/webcomponentsjs#^0.5.0" - }, - "devDependencies": { - "tools": "Polymer/tools#master", - "web-component-tester": "Polymer/web-component-tester#^1.4.2" - }, - "version": "0.5.2" -} \ No newline at end of file diff --git a/bower_components/connector/bower_components/polymer/build.log b/bower_components/connector/bower_components/polymer/build.log deleted file mode 100644 index e72f5bea..00000000 --- a/bower_components/connector/bower_components/polymer/build.log +++ /dev/null @@ -1,25 +0,0 @@ -BUILD LOG ---------- -Build Time: 2014-12-11T12:46:30 - -NODEJS INFORMATION -================== -nodejs: v0.10.33 -grunt: 0.4.5 -grunt-audit: 1.0.0 -grunt-contrib-concat: 0.5.0 -grunt-contrib-copy: 0.7.0 -grunt-contrib-uglify: 0.6.0 -grunt-string-replace: 1.0.0 - -REPO REVISIONS -============== -polymer-expressions: 197c3a0150e7a13374cfcc72e7066113723a623d -polymer-gestures: 17a6304916521be39409af292e8adf899bae0ce7 -polymer: a74e9f36526361dccb6df91be439ff9c3e043f41 - -BUILD HASHES -============ -dist/polymer.js: b9ad4c86af79c748cf4ea722f6d56671079fadf7 -dist/polymer.min.js: 2f2021ba9682b0bb702ee7fb68fb6fbfd288eac2 -dist/layout.html: 348d358a91712ecc2f8811efa430fcd954b4590c \ No newline at end of file diff --git a/bower_components/connector/bower_components/polymer/layout.html b/bower_components/connector/bower_components/polymer/layout.html deleted file mode 100644 index 55d4d2f0..00000000 --- a/bower_components/connector/bower_components/polymer/layout.html +++ /dev/null @@ -1,286 +0,0 @@ - - \ No newline at end of file diff --git a/bower_components/connector/bower_components/polymer/polymer.html b/bower_components/connector/bower_components/polymer/polymer.html deleted file mode 100644 index 7e3d8f1e..00000000 --- a/bower_components/connector/bower_components/polymer/polymer.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/bower_components/connector/bower_components/polymer/polymer.js b/bower_components/connector/bower_components/polymer/polymer.js deleted file mode 100644 index 2d3b692c..00000000 --- a/bower_components/connector/bower_components/polymer/polymer.js +++ /dev/null @@ -1,11851 +0,0 @@ -/** - * @license - * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - */ -// @version 0.5.1 -window.PolymerGestures = {}; - -(function(scope) { - var HAS_FULL_PATH = false; - - // test for full event path support - var pathTest = document.createElement('meta'); - if (pathTest.createShadowRoot) { - var sr = pathTest.createShadowRoot(); - var s = document.createElement('span'); - sr.appendChild(s); - pathTest.addEventListener('testpath', function(ev) { - if (ev.path) { - // if the span is in the event path, then path[0] is the real source for all events - HAS_FULL_PATH = ev.path[0] === s; - } - ev.stopPropagation(); - }); - var ev = new CustomEvent('testpath', {bubbles: true}); - // must add node to DOM to trigger event listener - document.head.appendChild(pathTest); - s.dispatchEvent(ev); - pathTest.parentNode.removeChild(pathTest); - sr = s = null; - } - pathTest = null; - - var target = { - shadow: function(inEl) { - if (inEl) { - return inEl.shadowRoot || inEl.webkitShadowRoot; - } - }, - canTarget: function(shadow) { - return shadow && Boolean(shadow.elementFromPoint); - }, - targetingShadow: function(inEl) { - var s = this.shadow(inEl); - if (this.canTarget(s)) { - return s; - } - }, - olderShadow: function(shadow) { - var os = shadow.olderShadowRoot; - if (!os) { - var se = shadow.querySelector('shadow'); - if (se) { - os = se.olderShadowRoot; - } - } - return os; - }, - allShadows: function(element) { - var shadows = [], s = this.shadow(element); - while(s) { - shadows.push(s); - s = this.olderShadow(s); - } - return shadows; - }, - searchRoot: function(inRoot, x, y) { - var t, st, sr, os; - if (inRoot) { - t = inRoot.elementFromPoint(x, y); - if (t) { - // found element, check if it has a ShadowRoot - sr = this.targetingShadow(t); - } else if (inRoot !== document) { - // check for sibling roots - sr = this.olderShadow(inRoot); - } - // search other roots, fall back to light dom element - return this.searchRoot(sr, x, y) || t; - } - }, - owner: function(element) { - if (!element) { - return document; - } - var s = element; - // walk up until you hit the shadow root or document - while (s.parentNode) { - s = s.parentNode; - } - // the owner element is expected to be a Document or ShadowRoot - if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) { - s = document; - } - return s; - }, - findTarget: function(inEvent) { - if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) { - return inEvent.path[0]; - } - var x = inEvent.clientX, y = inEvent.clientY; - // if the listener is in the shadow root, it is much faster to start there - var s = this.owner(inEvent.target); - // if x, y is not in this root, fall back to document search - if (!s.elementFromPoint(x, y)) { - s = document; - } - return this.searchRoot(s, x, y); - }, - findTouchAction: function(inEvent) { - var n; - if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) { - var path = inEvent.path; - for (var i = 0; i < path.length; i++) { - n = path[i]; - if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { - return n.getAttribute('touch-action'); - } - } - } else { - n = inEvent.target; - while(n) { - if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { - return n.getAttribute('touch-action'); - } - n = n.parentNode || n.host; - } - } - // auto is default - return "auto"; - }, - LCA: function(a, b) { - if (a === b) { - return a; - } - if (a && !b) { - return a; - } - if (b && !a) { - return b; - } - if (!b && !a) { - return document; - } - // fast case, a is a direct descendant of b or vice versa - if (a.contains && a.contains(b)) { - return a; - } - if (b.contains && b.contains(a)) { - return b; - } - var adepth = this.depth(a); - var bdepth = this.depth(b); - var d = adepth - bdepth; - if (d >= 0) { - a = this.walk(a, d); - } else { - b = this.walk(b, -d); - } - while (a && b && a !== b) { - a = a.parentNode || a.host; - b = b.parentNode || b.host; - } - return a; - }, - walk: function(n, u) { - for (var i = 0; n && (i < u); i++) { - n = n.parentNode || n.host; - } - return n; - }, - depth: function(n) { - var d = 0; - while(n) { - d++; - n = n.parentNode || n.host; - } - return d; - }, - deepContains: function(a, b) { - var common = this.LCA(a, b); - // if a is the common ancestor, it must "deeply" contain b - return common === a; - }, - insideNode: function(node, x, y) { - var rect = node.getBoundingClientRect(); - return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= rect.bottom); - }, - path: function(event) { - var p; - if (HAS_FULL_PATH && event.path && event.path.length) { - p = event.path; - } else { - p = []; - var n = this.findTarget(event); - while (n) { - p.push(n); - n = n.parentNode || n.host; - } - } - return p; - } - }; - scope.targetFinding = target; - /** - * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting - * - * @param {Event} Event An event object with clientX and clientY properties - * @return {Element} The probable event origninator - */ - scope.findTarget = target.findTarget.bind(target); - /** - * Determines if the "container" node deeply contains the "containee" node, including situations where the "containee" is contained by one or more ShadowDOM - * roots. - * - * @param {Node} container - * @param {Node} containee - * @return {Boolean} - */ - scope.deepContains = target.deepContains.bind(target); - - /** - * Determines if the x/y position is inside the given node. - * - * Example: - * - * function upHandler(event) { - * var innode = PolymerGestures.insideNode(event.target, event.clientX, event.clientY); - * if (innode) { - * // wait for tap? - * } else { - * // tap will never happen - * } - * } - * - * @param {Node} node - * @param {Number} x Screen X position - * @param {Number} y screen Y position - * @return {Boolean} - */ - scope.insideNode = target.insideNode; - -})(window.PolymerGestures); - -(function() { - function shadowSelector(v) { - return 'html /deep/ ' + selector(v); - } - function selector(v) { - return '[touch-action="' + v + '"]'; - } - function rule(v) { - return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; - } - var attrib2css = [ - 'none', - 'auto', - 'pan-x', - 'pan-y', - { - rule: 'pan-x pan-y', - selectors: [ - 'pan-x pan-y', - 'pan-y pan-x' - ] - }, - 'manipulation' - ]; - var styles = ''; - // only install stylesheet if the browser has touch action support - var hasTouchAction = typeof document.head.style.touchAction === 'string'; - // only add shadow selectors if shadowdom is supported - var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; - - if (hasTouchAction) { - attrib2css.forEach(function(r) { - if (String(r) === r) { - styles += selector(r) + rule(r) + '\n'; - if (hasShadowRoot) { - styles += shadowSelector(r) + rule(r) + '\n'; - } - } else { - styles += r.selectors.map(selector) + rule(r.rule) + '\n'; - if (hasShadowRoot) { - styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; - } - } - }); - - var el = document.createElement('style'); - el.textContent = styles; - document.head.appendChild(el); - } -})(); - -/** - * This is the constructor for new PointerEvents. - * - * New Pointer Events must be given a type, and an optional dictionary of - * initialization properties. - * - * Due to certain platform requirements, events returned from the constructor - * identify as MouseEvents. - * - * @constructor - * @param {String} inType The type of the event to create. - * @param {Object} [inDict] An optional dictionary of initial event properties. - * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`. - */ -(function(scope) { - - var MOUSE_PROPS = [ - 'bubbles', - 'cancelable', - 'view', - 'detail', - 'screenX', - 'screenY', - 'clientX', - 'clientY', - 'ctrlKey', - 'altKey', - 'shiftKey', - 'metaKey', - 'button', - 'relatedTarget', - 'pageX', - 'pageY' - ]; - - var MOUSE_DEFAULTS = [ - false, - false, - null, - null, - 0, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - null, - 0, - 0 - ]; - - var NOP_FACTORY = function(){ return function(){}; }; - - var eventFactory = { - // TODO(dfreedm): this is overridden by tap recognizer, needs review - preventTap: NOP_FACTORY, - makeBaseEvent: function(inType, inDict) { - var e = document.createEvent('Event'); - e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); - e.preventTap = eventFactory.preventTap(e); - return e; - }, - makeGestureEvent: function(inType, inDict) { - inDict = inDict || Object.create(null); - - var e = this.makeBaseEvent(inType, inDict); - for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { - k = keys[i]; - e[k] = inDict[k]; - } - return e; - }, - makePointerEvent: function(inType, inDict) { - inDict = inDict || Object.create(null); - - var e = this.makeBaseEvent(inType, inDict); - // define inherited MouseEvent properties - for(var i = 0, p; i < MOUSE_PROPS.length; i++) { - p = MOUSE_PROPS[i]; - e[p] = inDict[p] || MOUSE_DEFAULTS[i]; - } - e.buttons = inDict.buttons || 0; - - // Spec requires that pointers without pressure specified use 0.5 for down - // state and 0 for up state. - var pressure = 0; - if (inDict.pressure) { - pressure = inDict.pressure; - } else { - pressure = e.buttons ? 0.5 : 0; - } - - // add x/y properties aliased to clientX/Y - e.x = e.clientX; - e.y = e.clientY; - - // define the properties of the PointerEvent interface - e.pointerId = inDict.pointerId || 0; - e.width = inDict.width || 0; - e.height = inDict.height || 0; - e.pressure = pressure; - e.tiltX = inDict.tiltX || 0; - e.tiltY = inDict.tiltY || 0; - e.pointerType = inDict.pointerType || ''; - e.hwTimestamp = inDict.hwTimestamp || 0; - e.isPrimary = inDict.isPrimary || false; - e._source = inDict._source || ''; - return e; - } - }; - - scope.eventFactory = eventFactory; -})(window.PolymerGestures); - -/** - * This module implements an map of pointer states - */ -(function(scope) { - var USE_MAP = window.Map && window.Map.prototype.forEach; - var POINTERS_FN = function(){ return this.size; }; - function PointerMap() { - if (USE_MAP) { - var m = new Map(); - m.pointers = POINTERS_FN; - return m; - } else { - this.keys = []; - this.values = []; - } - } - - PointerMap.prototype = { - set: function(inId, inEvent) { - var i = this.keys.indexOf(inId); - if (i > -1) { - this.values[i] = inEvent; - } else { - this.keys.push(inId); - this.values.push(inEvent); - } - }, - has: function(inId) { - return this.keys.indexOf(inId) > -1; - }, - 'delete': function(inId) { - var i = this.keys.indexOf(inId); - if (i > -1) { - this.keys.splice(i, 1); - this.values.splice(i, 1); - } - }, - get: function(inId) { - var i = this.keys.indexOf(inId); - return this.values[i]; - }, - clear: function() { - this.keys.length = 0; - this.values.length = 0; - }, - // return value, key, map - forEach: function(callback, thisArg) { - this.values.forEach(function(v, i) { - callback.call(thisArg, v, this.keys[i], this); - }, this); - }, - pointers: function() { - return this.keys.length; - } - }; - - scope.PointerMap = PointerMap; -})(window.PolymerGestures); - -(function(scope) { - var CLONE_PROPS = [ - // MouseEvent - 'bubbles', - 'cancelable', - 'view', - 'detail', - 'screenX', - 'screenY', - 'clientX', - 'clientY', - 'ctrlKey', - 'altKey', - 'shiftKey', - 'metaKey', - 'button', - 'relatedTarget', - // DOM Level 3 - 'buttons', - // PointerEvent - 'pointerId', - 'width', - 'height', - 'pressure', - 'tiltX', - 'tiltY', - 'pointerType', - 'hwTimestamp', - 'isPrimary', - // event instance - 'type', - 'target', - 'currentTarget', - 'which', - 'pageX', - 'pageY', - 'timeStamp', - // gesture addons - 'preventTap', - 'tapPrevented', - '_source' - ]; - - var CLONE_DEFAULTS = [ - // MouseEvent - false, - false, - null, - null, - 0, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - null, - // DOM Level 3 - 0, - // PointerEvent - 0, - 0, - 0, - 0, - 0, - 0, - '', - 0, - false, - // event instance - '', - null, - null, - 0, - 0, - 0, - 0, - function(){}, - false - ]; - - var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); - - var eventFactory = scope.eventFactory; - - // set of recognizers to run for the currently handled event - var currentGestures; - - /** - * This module is for normalizing events. Mouse and Touch events will be - * collected here, and fire PointerEvents that have the same semantics, no - * matter the source. - * Events fired: - * - pointerdown: a pointing is added - * - pointerup: a pointer is removed - * - pointermove: a pointer is moved - * - pointerover: a pointer crosses into an element - * - pointerout: a pointer leaves an element - * - pointercancel: a pointer will no longer generate events - */ - var dispatcher = { - IS_IOS: false, - pointermap: new scope.PointerMap(), - requiredGestures: new scope.PointerMap(), - eventMap: Object.create(null), - // Scope objects for native events. - // This exists for ease of testing. - eventSources: Object.create(null), - eventSourceList: [], - gestures: [], - // map gesture event -> {listeners: int, index: gestures[int]} - dependencyMap: { - // make sure down and up are in the map to trigger "register" - down: {listeners: 0, index: -1}, - up: {listeners: 0, index: -1} - }, - gestureQueue: [], - /** - * Add a new event source that will generate pointer events. - * - * `inSource` must contain an array of event names named `events`, and - * functions with the names specified in the `events` array. - * @param {string} name A name for the event source - * @param {Object} source A new source of platform events. - */ - registerSource: function(name, source) { - var s = source; - var newEvents = s.events; - if (newEvents) { - newEvents.forEach(function(e) { - if (s[e]) { - this.eventMap[e] = s[e].bind(s); - } - }, this); - this.eventSources[name] = s; - this.eventSourceList.push(s); - } - }, - registerGesture: function(name, source) { - var obj = Object.create(null); - obj.listeners = 0; - obj.index = this.gestures.length; - for (var i = 0, g; i < source.exposes.length; i++) { - g = source.exposes[i].toLowerCase(); - this.dependencyMap[g] = obj; - } - this.gestures.push(source); - }, - register: function(element, initial) { - var l = this.eventSourceList.length; - for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { - // call eventsource register - es.register.call(es, element, initial); - } - }, - unregister: function(element) { - var l = this.eventSourceList.length; - for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { - // call eventsource register - es.unregister.call(es, element); - } - }, - // EVENTS - down: function(inEvent) { - this.requiredGestures.set(inEvent.pointerId, currentGestures); - this.fireEvent('down', inEvent); - }, - move: function(inEvent) { - // pipe move events into gesture queue directly - inEvent.type = 'move'; - this.fillGestureQueue(inEvent); - }, - up: function(inEvent) { - this.fireEvent('up', inEvent); - this.requiredGestures.delete(inEvent.pointerId); - }, - cancel: function(inEvent) { - inEvent.tapPrevented = true; - this.fireEvent('up', inEvent); - this.requiredGestures.delete(inEvent.pointerId); - }, - addGestureDependency: function(node, currentGestures) { - var gesturesWanted = node._pgEvents; - if (gesturesWanted && currentGestures) { - var gk = Object.keys(gesturesWanted); - for (var i = 0, r, ri, g; i < gk.length; i++) { - // gesture - g = gk[i]; - if (gesturesWanted[g] > 0) { - // lookup gesture recognizer - r = this.dependencyMap[g]; - // recognizer index - ri = r ? r.index : -1; - currentGestures[ri] = true; - } - } - } - }, - // LISTENER LOGIC - eventHandler: function(inEvent) { - // This is used to prevent multiple dispatch of events from - // platform events. This can happen when two elements in different scopes - // are set up to create pointer events, which is relevant to Shadow DOM. - - var type = inEvent.type; - - // only generate the list of desired events on "down" - if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown' || type === 'MSPointerDown') { - if (!inEvent._handledByPG) { - currentGestures = {}; - } - - // in IOS mode, there is only a listener on the document, so this is not re-entrant - if (this.IS_IOS) { - var ev = inEvent; - if (type === 'touchstart') { - var ct = inEvent.changedTouches[0]; - // set up a fake event to give to the path builder - ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clientY, path: inEvent.path}; - } - // use event path if available, otherwise build a path from target finding - var nodes = inEvent.path || scope.targetFinding.path(ev); - for (var i = 0, n; i < nodes.length; i++) { - n = nodes[i]; - this.addGestureDependency(n, currentGestures); - } - } else { - this.addGestureDependency(inEvent.currentTarget, currentGestures); - } - } - - if (inEvent._handledByPG) { - return; - } - var fn = this.eventMap && this.eventMap[type]; - if (fn) { - fn(inEvent); - } - inEvent._handledByPG = true; - }, - // set up event listeners - listen: function(target, events) { - for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { - this.addEvent(target, e); - } - }, - // remove event listeners - unlisten: function(target, events) { - for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { - this.removeEvent(target, e); - } - }, - addEvent: function(target, eventName) { - target.addEventListener(eventName, this.boundHandler); - }, - removeEvent: function(target, eventName) { - target.removeEventListener(eventName, this.boundHandler); - }, - // EVENT CREATION AND TRACKING - /** - * Creates a new Event of type `inType`, based on the information in - * `inEvent`. - * - * @param {string} inType A string representing the type of event to create - * @param {Event} inEvent A platform event with a target - * @return {Event} A PointerEvent of type `inType` - */ - makeEvent: function(inType, inEvent) { - var e = eventFactory.makePointerEvent(inType, inEvent); - e.preventDefault = inEvent.preventDefault; - e.tapPrevented = inEvent.tapPrevented; - e._target = e._target || inEvent.target; - return e; - }, - // make and dispatch an event in one call - fireEvent: function(inType, inEvent) { - var e = this.makeEvent(inType, inEvent); - return this.dispatchEvent(e); - }, - /** - * Returns a snapshot of inEvent, with writable properties. - * - * @param {Event} inEvent An event that contains properties to copy. - * @return {Object} An object containing shallow copies of `inEvent`'s - * properties. - */ - cloneEvent: function(inEvent) { - var eventCopy = Object.create(null), p; - for (var i = 0; i < CLONE_PROPS.length; i++) { - p = CLONE_PROPS[i]; - eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; - // Work around SVGInstanceElement shadow tree - // Return the element that is represented by the instance for Safari, Chrome, IE. - // This is the behavior implemented by Firefox. - if (p === 'target' || p === 'relatedTarget') { - if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { - eventCopy[p] = eventCopy[p].correspondingUseElement; - } - } - } - // keep the semantics of preventDefault - eventCopy.preventDefault = function() { - inEvent.preventDefault(); - }; - return eventCopy; - }, - /** - * Dispatches the event to its target. - * - * @param {Event} inEvent The event to be dispatched. - * @return {Boolean} True if an event handler returns true, false otherwise. - */ - dispatchEvent: function(inEvent) { - var t = inEvent._target; - if (t) { - t.dispatchEvent(inEvent); - // clone the event for the gesture system to process - // clone after dispatch to pick up gesture prevention code - var clone = this.cloneEvent(inEvent); - clone.target = t; - this.fillGestureQueue(clone); - } - }, - gestureTrigger: function() { - // process the gesture queue - for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { - e = this.gestureQueue[i]; - rg = e._requiredGestures; - if (rg) { - for (var j = 0, g, fn; j < this.gestures.length; j++) { - // only run recognizer if an element in the source event's path is listening for those gestures - if (rg[j]) { - g = this.gestures[j]; - fn = g[e.type]; - if (fn) { - fn.call(g, e); - } - } - } - } - } - this.gestureQueue.length = 0; - }, - fillGestureQueue: function(ev) { - // only trigger the gesture queue once - if (!this.gestureQueue.length) { - requestAnimationFrame(this.boundGestureTrigger); - } - ev._requiredGestures = this.requiredGestures.get(ev.pointerId); - this.gestureQueue.push(ev); - } - }; - dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); - dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); - scope.dispatcher = dispatcher; - - /** - * Listen for `gesture` on `node` with the `handler` function - * - * If `handler` is the first listener for `gesture`, the underlying gesture recognizer is then enabled. - * - * @param {Element} node - * @param {string} gesture - * @return Boolean `gesture` is a valid gesture - */ - scope.activateGesture = function(node, gesture) { - var g = gesture.toLowerCase(); - var dep = dispatcher.dependencyMap[g]; - if (dep) { - var recognizer = dispatcher.gestures[dep.index]; - if (!node._pgListeners) { - dispatcher.register(node); - node._pgListeners = 0; - } - // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes - if (recognizer) { - var touchAction = recognizer.defaultActions && recognizer.defaultActions[g]; - var actionNode; - switch(node.nodeType) { - case Node.ELEMENT_NODE: - actionNode = node; - break; - case Node.DOCUMENT_FRAGMENT_NODE: - actionNode = node.host; - break; - default: - actionNode = null; - break; - } - if (touchAction && actionNode && !actionNode.hasAttribute('touch-action')) { - actionNode.setAttribute('touch-action', touchAction); - } - } - if (!node._pgEvents) { - node._pgEvents = {}; - } - node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; - node._pgListeners++; - } - return Boolean(dep); - }; - - /** - * - * Listen for `gesture` from `node` with `handler` function. - * - * @param {Element} node - * @param {string} gesture - * @param {Function} handler - * @param {Boolean} capture - */ - scope.addEventListener = function(node, gesture, handler, capture) { - if (handler) { - scope.activateGesture(node, gesture); - node.addEventListener(gesture, handler, capture); - } - }; - - /** - * Tears down the gesture configuration for `node` - * - * If `handler` is the last listener for `gesture`, the underlying gesture recognizer is disabled. - * - * @param {Element} node - * @param {string} gesture - * @return Boolean `gesture` is a valid gesture - */ - scope.deactivateGesture = function(node, gesture) { - var g = gesture.toLowerCase(); - var dep = dispatcher.dependencyMap[g]; - if (dep) { - if (node._pgListeners > 0) { - node._pgListeners--; - } - if (node._pgListeners === 0) { - dispatcher.unregister(node); - } - if (node._pgEvents) { - if (node._pgEvents[g] > 0) { - node._pgEvents[g]--; - } else { - node._pgEvents[g] = 0; - } - } - } - return Boolean(dep); - }; - - /** - * Stop listening for `gesture` from `node` with `handler` function. - * - * @param {Element} node - * @param {string} gesture - * @param {Function} handler - * @param {Boolean} capture - */ - scope.removeEventListener = function(node, gesture, handler, capture) { - if (handler) { - scope.deactivateGesture(node, gesture); - node.removeEventListener(gesture, handler, capture); - } - }; -})(window.PolymerGestures); - -(function(scope) { - var dispatcher = scope.dispatcher; - var pointermap = dispatcher.pointermap; - // radius around touchend that swallows mouse events - var DEDUP_DIST = 25; - - var WHICH_TO_BUTTONS = [0, 1, 4, 2]; - - var CURRENT_BUTTONS = 0; - - var FIREFOX_LINUX = /Linux.*Firefox\//i; - - var HAS_BUTTONS = (function() { - // firefox on linux returns spec-incorrect values for mouseup.buttons - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_also - // https://codereview.chromium.org/727593003/#msg16 - if (FIREFOX_LINUX.test(navigator.userAgent)) { - return false; - } - try { - return new MouseEvent('test', {buttons: 1}).buttons === 1; - } catch (e) { - return false; - } - })(); - - // handler block for native mouse events - var mouseEvents = { - POINTER_ID: 1, - POINTER_TYPE: 'mouse', - events: [ - 'mousedown', - 'mousemove', - 'mouseup' - ], - exposes: [ - 'down', - 'up', - 'move' - ], - register: function(target) { - dispatcher.listen(target, this.events); - }, - unregister: function(target) { - if (target === document) { - return; - } - dispatcher.unlisten(target, this.events); - }, - lastTouches: [], - // collide with the global mouse listener - isEventSimulatedFromTouch: function(inEvent) { - var lts = this.lastTouches; - var x = inEvent.clientX, y = inEvent.clientY; - for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { - // simulated mouse events will be swallowed near a primary touchend - var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); - if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { - return true; - } - } - }, - prepareEvent: function(inEvent) { - var e = dispatcher.cloneEvent(inEvent); - e.pointerId = this.POINTER_ID; - e.isPrimary = true; - e.pointerType = this.POINTER_TYPE; - e._source = 'mouse'; - if (!HAS_BUTTONS) { - var type = inEvent.type; - var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; - if (type === 'mousedown') { - CURRENT_BUTTONS |= bit; - } else if (type === 'mouseup') { - CURRENT_BUTTONS &= ~bit; - } - e.buttons = CURRENT_BUTTONS; - } - return e; - }, - mousedown: function(inEvent) { - if (!this.isEventSimulatedFromTouch(inEvent)) { - var p = pointermap.has(this.POINTER_ID); - var e = this.prepareEvent(inEvent); - e.target = scope.findTarget(inEvent); - pointermap.set(this.POINTER_ID, e.target); - dispatcher.down(e); - } - }, - mousemove: function(inEvent) { - if (!this.isEventSimulatedFromTouch(inEvent)) { - var target = pointermap.get(this.POINTER_ID); - if (target) { - var e = this.prepareEvent(inEvent); - e.target = target; - // handle case where we missed a mouseup - if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { - if (!HAS_BUTTONS) { - CURRENT_BUTTONS = e.buttons = 0; - } - dispatcher.cancel(e); - this.cleanupMouse(e.buttons); - } else { - dispatcher.move(e); - } - } - } - }, - mouseup: function(inEvent) { - if (!this.isEventSimulatedFromTouch(inEvent)) { - var e = this.prepareEvent(inEvent); - e.relatedTarget = scope.findTarget(inEvent); - e.target = pointermap.get(this.POINTER_ID); - dispatcher.up(e); - this.cleanupMouse(e.buttons); - } - }, - cleanupMouse: function(buttons) { - if (buttons === 0) { - pointermap.delete(this.POINTER_ID); - } - } - }; - - scope.mouseEvents = mouseEvents; -})(window.PolymerGestures); - -(function(scope) { - var dispatcher = scope.dispatcher; - var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); - var pointermap = dispatcher.pointermap; - var touchMap = Array.prototype.map.call.bind(Array.prototype.map); - // This should be long enough to ignore compat mouse events made by touch - var DEDUP_TIMEOUT = 2500; - var DEDUP_DIST = 25; - var CLICK_COUNT_TIMEOUT = 200; - var HYSTERESIS = 20; - var ATTRIB = 'touch-action'; - // TODO(dfreedm): disable until http://crbug.com/399765 is resolved - // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; - var HAS_TOUCH_ACTION = false; - - // handler block for native touch events - var touchEvents = { - IS_IOS: false, - events: [ - 'touchstart', - 'touchmove', - 'touchend', - 'touchcancel' - ], - exposes: [ - 'down', - 'up', - 'move' - ], - register: function(target, initial) { - if (this.IS_IOS ? initial : !initial) { - dispatcher.listen(target, this.events); - } - }, - unregister: function(target) { - if (!this.IS_IOS) { - dispatcher.unlisten(target, this.events); - } - }, - scrollTypes: { - EMITTER: 'none', - XSCROLLER: 'pan-x', - YSCROLLER: 'pan-y', - }, - touchActionToScrollType: function(touchAction) { - var t = touchAction; - var st = this.scrollTypes; - if (t === st.EMITTER) { - return 'none'; - } else if (t === st.XSCROLLER) { - return 'X'; - } else if (t === st.YSCROLLER) { - return 'Y'; - } else { - return 'XY'; - } - }, - POINTER_TYPE: 'touch', - firstTouch: null, - isPrimaryTouch: function(inTouch) { - return this.firstTouch === inTouch.identifier; - }, - setPrimaryTouch: function(inTouch) { - // set primary touch if there no pointers, or the only pointer is the mouse - if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) { - this.firstTouch = inTouch.identifier; - this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; - this.firstTarget = inTouch.target; - this.scrolling = null; - this.cancelResetClickCount(); - } - }, - removePrimaryPointer: function(inPointer) { - if (inPointer.isPrimary) { - this.firstTouch = null; - this.firstXY = null; - this.resetClickCount(); - } - }, - clickCount: 0, - resetId: null, - resetClickCount: function() { - var fn = function() { - this.clickCount = 0; - this.resetId = null; - }.bind(this); - this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); - }, - cancelResetClickCount: function() { - if (this.resetId) { - clearTimeout(this.resetId); - } - }, - typeToButtons: function(type) { - var ret = 0; - if (type === 'touchstart' || type === 'touchmove') { - ret = 1; - } - return ret; - }, - findTarget: function(touch, id) { - if (this.currentTouchEvent.type === 'touchstart') { - if (this.isPrimaryTouch(touch)) { - var fastPath = { - clientX: touch.clientX, - clientY: touch.clientY, - path: this.currentTouchEvent.path, - target: this.currentTouchEvent.target - }; - return scope.findTarget(fastPath); - } else { - return scope.findTarget(touch); - } - } - // reuse target we found in touchstart - return pointermap.get(id); - }, - touchToPointer: function(inTouch) { - var cte = this.currentTouchEvent; - var e = dispatcher.cloneEvent(inTouch); - // Spec specifies that pointerId 1 is reserved for Mouse. - // Touch identifiers can start at 0. - // Add 2 to the touch identifier for compatibility. - var id = e.pointerId = inTouch.identifier + 2; - e.target = this.findTarget(inTouch, id); - e.bubbles = true; - e.cancelable = true; - e.detail = this.clickCount; - e.buttons = this.typeToButtons(cte.type); - e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; - e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; - e.pressure = inTouch.webkitForce || inTouch.force || 0.5; - e.isPrimary = this.isPrimaryTouch(inTouch); - e.pointerType = this.POINTER_TYPE; - e._source = 'touch'; - // forward touch preventDefaults - var self = this; - e.preventDefault = function() { - self.scrolling = false; - self.firstXY = null; - cte.preventDefault(); - }; - return e; - }, - processTouches: function(inEvent, inFunction) { - var tl = inEvent.changedTouches; - this.currentTouchEvent = inEvent; - for (var i = 0, t, p; i < tl.length; i++) { - t = tl[i]; - p = this.touchToPointer(t); - if (inEvent.type === 'touchstart') { - pointermap.set(p.pointerId, p.target); - } - if (pointermap.has(p.pointerId)) { - inFunction.call(this, p); - } - if (inEvent.type === 'touchend' || inEvent._cancel) { - this.cleanUpPointer(p); - } - } - }, - // For single axis scrollers, determines whether the element should emit - // pointer events or behave as a scroller - shouldScroll: function(inEvent) { - if (this.firstXY) { - var ret; - var touchAction = scope.targetFinding.findTouchAction(inEvent); - var scrollAxis = this.touchActionToScrollType(touchAction); - if (scrollAxis === 'none') { - // this element is a touch-action: none, should never scroll - ret = false; - } else if (scrollAxis === 'XY') { - // this element should always scroll - ret = true; - } else { - var t = inEvent.changedTouches[0]; - // check the intended scroll axis, and other axis - var a = scrollAxis; - var oa = scrollAxis === 'Y' ? 'X' : 'Y'; - var da = Math.abs(t['client' + a] - this.firstXY[a]); - var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); - // if delta in the scroll axis > delta other axis, scroll instead of - // making events - ret = da >= doa; - } - return ret; - } - }, - findTouch: function(inTL, inId) { - for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { - if (t.identifier === inId) { - return true; - } - } - }, - // In some instances, a touchstart can happen without a touchend. This - // leaves the pointermap in a broken state. - // Therefore, on every touchstart, we remove the touches that did not fire a - // touchend event. - // To keep state globally consistent, we fire a - // pointercancel for this "abandoned" touch - vacuumTouches: function(inEvent) { - var tl = inEvent.touches; - // pointermap.pointers() should be < tl.length here, as the touchstart has not - // been processed yet. - if (pointermap.pointers() >= tl.length) { - var d = []; - pointermap.forEach(function(value, key) { - // Never remove pointerId == 1, which is mouse. - // Touch identifiers are 2 smaller than their pointerId, which is the - // index in pointermap. - if (key !== 1 && !this.findTouch(tl, key - 2)) { - var p = value; - d.push(p); - } - }, this); - d.forEach(function(p) { - this.cancel(p); - pointermap.delete(p.pointerId); - }, this); - } - }, - touchstart: function(inEvent) { - this.vacuumTouches(inEvent); - this.setPrimaryTouch(inEvent.changedTouches[0]); - this.dedupSynthMouse(inEvent); - if (!this.scrolling) { - this.clickCount++; - this.processTouches(inEvent, this.down); - } - }, - down: function(inPointer) { - dispatcher.down(inPointer); - }, - touchmove: function(inEvent) { - if (HAS_TOUCH_ACTION) { - // touchevent.cancelable == false is sent when the page is scrolling under native Touch Action in Chrome 36 - // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/b9kmtwM1jJQJ - if (inEvent.cancelable) { - this.processTouches(inEvent, this.move); - } - } else { - if (!this.scrolling) { - if (this.scrolling === null && this.shouldScroll(inEvent)) { - this.scrolling = true; - } else { - this.scrolling = false; - inEvent.preventDefault(); - this.processTouches(inEvent, this.move); - } - } else if (this.firstXY) { - var t = inEvent.changedTouches[0]; - var dx = t.clientX - this.firstXY.X; - var dy = t.clientY - this.firstXY.Y; - var dd = Math.sqrt(dx * dx + dy * dy); - if (dd >= HYSTERESIS) { - this.touchcancel(inEvent); - this.scrolling = true; - this.firstXY = null; - } - } - } - }, - move: function(inPointer) { - dispatcher.move(inPointer); - }, - touchend: function(inEvent) { - this.dedupSynthMouse(inEvent); - this.processTouches(inEvent, this.up); - }, - up: function(inPointer) { - inPointer.relatedTarget = scope.findTarget(inPointer); - dispatcher.up(inPointer); - }, - cancel: function(inPointer) { - dispatcher.cancel(inPointer); - }, - touchcancel: function(inEvent) { - inEvent._cancel = true; - this.processTouches(inEvent, this.cancel); - }, - cleanUpPointer: function(inPointer) { - pointermap['delete'](inPointer.pointerId); - this.removePrimaryPointer(inPointer); - }, - // prevent synth mouse events from creating pointer events - dedupSynthMouse: function(inEvent) { - var lts = scope.mouseEvents.lastTouches; - var t = inEvent.changedTouches[0]; - // only the primary finger will synth mouse events - if (this.isPrimaryTouch(t)) { - // remember x/y of last touch - var lt = {x: t.clientX, y: t.clientY}; - lts.push(lt); - var fn = (function(lts, lt){ - var i = lts.indexOf(lt); - if (i > -1) { - lts.splice(i, 1); - } - }).bind(null, lts, lt); - setTimeout(fn, DEDUP_TIMEOUT); - } - } - }; - - // prevent "ghost clicks" that come from elements that were removed in a touch handler - var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype.stopPropagation; - document.addEventListener('click', function(ev) { - var x = ev.clientX, y = ev.clientY; - // check if a click is within DEDUP_DIST px radius of the touchstart - var closeTo = function(touch) { - var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); - return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); - }; - // if click coordinates are close to touch coordinates, assume the click came from a touch - var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); - // if the click came from touch, and the touchstart target is not in the path of the click event, - // then the touchstart target was probably removed, and the click should be "busted" - var path = scope.targetFinding.path(ev); - if (wasTouched) { - for (var i = 0; i < path.length; i++) { - if (path[i] === touchEvents.firstTarget) { - return; - } - } - ev.preventDefault(); - STOP_PROP_FN.call(ev); - } - }, true); - - scope.touchEvents = touchEvents; -})(window.PolymerGestures); - -(function(scope) { - var dispatcher = scope.dispatcher; - var pointermap = dispatcher.pointermap; - var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; - var msEvents = { - events: [ - 'MSPointerDown', - 'MSPointerMove', - 'MSPointerUp', - 'MSPointerCancel', - ], - register: function(target) { - dispatcher.listen(target, this.events); - }, - unregister: function(target) { - if (target === document) { - return; - } - dispatcher.unlisten(target, this.events); - }, - POINTER_TYPES: [ - '', - 'unavailable', - 'touch', - 'pen', - 'mouse' - ], - prepareEvent: function(inEvent) { - var e = inEvent; - e = dispatcher.cloneEvent(inEvent); - if (HAS_BITMAP_TYPE) { - e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; - } - e._source = 'ms'; - return e; - }, - cleanup: function(id) { - pointermap['delete'](id); - }, - MSPointerDown: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.target = scope.findTarget(inEvent); - pointermap.set(inEvent.pointerId, e.target); - dispatcher.down(e); - }, - MSPointerMove: function(inEvent) { - var target = pointermap.get(inEvent.pointerId); - if (target) { - var e = this.prepareEvent(inEvent); - e.target = target; - dispatcher.move(e); - } - }, - MSPointerUp: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.relatedTarget = scope.findTarget(inEvent); - e.target = pointermap.get(e.pointerId); - dispatcher.up(e); - this.cleanup(inEvent.pointerId); - }, - MSPointerCancel: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.relatedTarget = scope.findTarget(inEvent); - e.target = pointermap.get(e.pointerId); - dispatcher.cancel(e); - this.cleanup(inEvent.pointerId); - } - }; - - scope.msEvents = msEvents; -})(window.PolymerGestures); - -(function(scope) { - var dispatcher = scope.dispatcher; - var pointermap = dispatcher.pointermap; - var pointerEvents = { - events: [ - 'pointerdown', - 'pointermove', - 'pointerup', - 'pointercancel' - ], - prepareEvent: function(inEvent) { - var e = dispatcher.cloneEvent(inEvent); - e._source = 'pointer'; - return e; - }, - register: function(target) { - dispatcher.listen(target, this.events); - }, - unregister: function(target) { - if (target === document) { - return; - } - dispatcher.unlisten(target, this.events); - }, - cleanup: function(id) { - pointermap['delete'](id); - }, - pointerdown: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.target = scope.findTarget(inEvent); - pointermap.set(e.pointerId, e.target); - dispatcher.down(e); - }, - pointermove: function(inEvent) { - var target = pointermap.get(inEvent.pointerId); - if (target) { - var e = this.prepareEvent(inEvent); - e.target = target; - dispatcher.move(e); - } - }, - pointerup: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.relatedTarget = scope.findTarget(inEvent); - e.target = pointermap.get(e.pointerId); - dispatcher.up(e); - this.cleanup(inEvent.pointerId); - }, - pointercancel: function(inEvent) { - var e = this.prepareEvent(inEvent); - e.relatedTarget = scope.findTarget(inEvent); - e.target = pointermap.get(e.pointerId); - dispatcher.cancel(e); - this.cleanup(inEvent.pointerId); - } - }; - - scope.pointerEvents = pointerEvents; -})(window.PolymerGestures); - -/** - * This module contains the handlers for native platform events. - * From here, the dispatcher is called to create unified pointer events. - * Included are touch events (v1), mouse events, and MSPointerEvents. - */ -(function(scope) { - - var dispatcher = scope.dispatcher; - var nav = window.navigator; - - if (window.PointerEvent) { - dispatcher.registerSource('pointer', scope.pointerEvents); - } else if (nav.msPointerEnabled) { - dispatcher.registerSource('ms', scope.msEvents); - } else { - dispatcher.registerSource('mouse', scope.mouseEvents); - if (window.ontouchstart !== undefined) { - dispatcher.registerSource('touch', scope.touchEvents); - } - } - - // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and https://bugs.webkit.org/show_bug.cgi?id=136506 - var ua = navigator.userAgent; - var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; - - dispatcher.IS_IOS = IS_IOS; - scope.touchEvents.IS_IOS = IS_IOS; - - dispatcher.register(document, true); -})(window.PolymerGestures); - -/** - * This event denotes the beginning of a series of tracking events. - * - * @module PointerGestures - * @submodule Events - * @class trackstart - */ -/** - * Pixels moved in the x direction since trackstart. - * @type Number - * @property dx - */ -/** - * Pixes moved in the y direction since trackstart. - * @type Number - * @property dy - */ -/** - * Pixels moved in the x direction since the last track. - * @type Number - * @property ddx - */ -/** - * Pixles moved in the y direction since the last track. - * @type Number - * @property ddy - */ -/** - * The clientX position of the track gesture. - * @type Number - * @property clientX - */ -/** - * The clientY position of the track gesture. - * @type Number - * @property clientY - */ -/** - * The pageX position of the track gesture. - * @type Number - * @property pageX - */ -/** - * The pageY position of the track gesture. - * @type Number - * @property pageY - */ -/** - * The screenX position of the track gesture. - * @type Number - * @property screenX - */ -/** - * The screenY position of the track gesture. - * @type Number - * @property screenY - */ -/** - * The last x axis direction of the pointer. - * @type Number - * @property xDirection - */ -/** - * The last y axis direction of the pointer. - * @type Number - * @property yDirection - */ -/** - * A shared object between all tracking events. - * @type Object - * @property trackInfo - */ -/** - * The element currently under the pointer. - * @type Element - * @property relatedTarget - */ -/** - * The type of pointer that make the track gesture. - * @type String - * @property pointerType - */ -/** - * - * This event fires for all pointer movement being tracked. - * - * @class track - * @extends trackstart - */ -/** - * This event fires when the pointer is no longer being tracked. - * - * @class trackend - * @extends trackstart - */ - - (function(scope) { - var dispatcher = scope.dispatcher; - var eventFactory = scope.eventFactory; - var pointermap = new scope.PointerMap(); - var track = { - events: [ - 'down', - 'move', - 'up', - ], - exposes: [ - 'trackstart', - 'track', - 'trackx', - 'tracky', - 'trackend' - ], - defaultActions: { - 'track': 'none', - 'trackx': 'pan-y', - 'tracky': 'pan-x' - }, - WIGGLE_THRESHOLD: 4, - clampDir: function(inDelta) { - return inDelta > 0 ? 1 : -1; - }, - calcPositionDelta: function(inA, inB) { - var x = 0, y = 0; - if (inA && inB) { - x = inB.pageX - inA.pageX; - y = inB.pageY - inA.pageY; - } - return {x: x, y: y}; - }, - fireTrack: function(inType, inEvent, inTrackingData) { - var t = inTrackingData; - var d = this.calcPositionDelta(t.downEvent, inEvent); - var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); - if (dd.x) { - t.xDirection = this.clampDir(dd.x); - } else if (inType === 'trackx') { - return; - } - if (dd.y) { - t.yDirection = this.clampDir(dd.y); - } else if (inType === 'tracky') { - return; - } - var gestureProto = { - bubbles: true, - cancelable: true, - trackInfo: t.trackInfo, - relatedTarget: inEvent.relatedTarget, - pointerType: inEvent.pointerType, - pointerId: inEvent.pointerId, - _source: 'track' - }; - if (inType !== 'tracky') { - gestureProto.x = inEvent.x; - gestureProto.dx = d.x; - gestureProto.ddx = dd.x; - gestureProto.clientX = inEvent.clientX; - gestureProto.pageX = inEvent.pageX; - gestureProto.screenX = inEvent.screenX; - gestureProto.xDirection = t.xDirection; - } - if (inType !== 'trackx') { - gestureProto.dy = d.y; - gestureProto.ddy = dd.y; - gestureProto.y = inEvent.y; - gestureProto.clientY = inEvent.clientY; - gestureProto.pageY = inEvent.pageY; - gestureProto.screenY = inEvent.screenY; - gestureProto.yDirection = t.yDirection; - } - var e = eventFactory.makeGestureEvent(inType, gestureProto); - t.downTarget.dispatchEvent(e); - }, - down: function(inEvent) { - if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) { - var p = { - downEvent: inEvent, - downTarget: inEvent.target, - trackInfo: {}, - lastMoveEvent: null, - xDirection: 0, - yDirection: 0, - tracking: false - }; - pointermap.set(inEvent.pointerId, p); - } - }, - move: function(inEvent) { - var p = pointermap.get(inEvent.pointerId); - if (p) { - if (!p.tracking) { - var d = this.calcPositionDelta(p.downEvent, inEvent); - var move = d.x * d.x + d.y * d.y; - // start tracking only if finger moves more than WIGGLE_THRESHOLD - if (move > this.WIGGLE_THRESHOLD) { - p.tracking = true; - p.lastMoveEvent = p.downEvent; - this.fireTrack('trackstart', inEvent, p); - } - } - if (p.tracking) { - this.fireTrack('track', inEvent, p); - this.fireTrack('trackx', inEvent, p); - this.fireTrack('tracky', inEvent, p); - } - p.lastMoveEvent = inEvent; - } - }, - up: function(inEvent) { - var p = pointermap.get(inEvent.pointerId); - if (p) { - if (p.tracking) { - this.fireTrack('trackend', inEvent, p); - } - pointermap.delete(inEvent.pointerId); - } - } - }; - dispatcher.registerGesture('track', track); - })(window.PolymerGestures); - -/** - * This event is fired when a pointer is held down for 200ms. - * - * @module PointerGestures - * @submodule Events - * @class hold - */ -/** - * Type of pointer that made the holding event. - * @type String - * @property pointerType - */ -/** - * Screen X axis position of the held pointer - * @type Number - * @property clientX - */ -/** - * Screen Y axis position of the held pointer - * @type Number - * @property clientY - */ -/** - * Type of pointer that made the holding event. - * @type String - * @property pointerType - */ -/** - * This event is fired every 200ms while a pointer is held down. - * - * @class holdpulse - * @extends hold - */ -/** - * Milliseconds pointer has been held down. - * @type Number - * @property holdTime - */ -/** - * This event is fired when a held pointer is released or moved. - * - * @class release - */ - -(function(scope) { - var dispatcher = scope.dispatcher; - var eventFactory = scope.eventFactory; - var hold = { - // wait at least HOLD_DELAY ms between hold and pulse events - HOLD_DELAY: 200, - // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold - WIGGLE_THRESHOLD: 16, - events: [ - 'down', - 'move', - 'up', - ], - exposes: [ - 'hold', - 'holdpulse', - 'release' - ], - heldPointer: null, - holdJob: null, - pulse: function() { - var hold = Date.now() - this.heldPointer.timeStamp; - var type = this.held ? 'holdpulse' : 'hold'; - this.fireHold(type, hold); - this.held = true; - }, - cancel: function() { - clearInterval(this.holdJob); - if (this.held) { - this.fireHold('release'); - } - this.held = false; - this.heldPointer = null; - this.target = null; - this.holdJob = null; - }, - down: function(inEvent) { - if (inEvent.isPrimary && !this.heldPointer) { - this.heldPointer = inEvent; - this.target = inEvent.target; - this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); - } - }, - up: function(inEvent) { - if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { - this.cancel(); - } - }, - move: function(inEvent) { - if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { - var x = inEvent.clientX - this.heldPointer.clientX; - var y = inEvent.clientY - this.heldPointer.clientY; - if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { - this.cancel(); - } - } - }, - fireHold: function(inType, inHoldTime) { - var p = { - bubbles: true, - cancelable: true, - pointerType: this.heldPointer.pointerType, - pointerId: this.heldPointer.pointerId, - x: this.heldPointer.clientX, - y: this.heldPointer.clientY, - _source: 'hold' - }; - if (inHoldTime) { - p.holdTime = inHoldTime; - } - var e = eventFactory.makeGestureEvent(inType, p); - this.target.dispatchEvent(e); - } - }; - dispatcher.registerGesture('hold', hold); -})(window.PolymerGestures); - -/** - * This event is fired when a pointer quickly goes down and up, and is used to - * denote activation. - * - * Any gesture event can prevent the tap event from being created by calling - * `event.preventTap`. - * - * Any pointer event can prevent the tap by setting the `tapPrevented` property - * on itself. - * - * @module PointerGestures - * @submodule Events - * @class tap - */ -/** - * X axis position of the tap. - * @property x - * @type Number - */ -/** - * Y axis position of the tap. - * @property y - * @type Number - */ -/** - * Type of the pointer that made the tap. - * @property pointerType - * @type String - */ -(function(scope) { - var dispatcher = scope.dispatcher; - var eventFactory = scope.eventFactory; - var pointermap = new scope.PointerMap(); - var tap = { - events: [ - 'down', - 'up' - ], - exposes: [ - 'tap' - ], - down: function(inEvent) { - if (inEvent.isPrimary && !inEvent.tapPrevented) { - pointermap.set(inEvent.pointerId, { - target: inEvent.target, - buttons: inEvent.buttons, - x: inEvent.clientX, - y: inEvent.clientY - }); - } - }, - shouldTap: function(e, downState) { - var tap = true; - if (e.pointerType === 'mouse') { - // only allow left click to tap for mouse - tap = (e.buttons ^ 1) && (downState.buttons & 1); - } - return tap && !e.tapPrevented; - }, - up: function(inEvent) { - var start = pointermap.get(inEvent.pointerId); - if (start && this.shouldTap(inEvent, start)) { - // up.relatedTarget is target currently under finger - var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); - if (t) { - var e = eventFactory.makeGestureEvent('tap', { - bubbles: true, - cancelable: true, - x: inEvent.clientX, - y: inEvent.clientY, - detail: inEvent.detail, - pointerType: inEvent.pointerType, - pointerId: inEvent.pointerId, - altKey: inEvent.altKey, - ctrlKey: inEvent.ctrlKey, - metaKey: inEvent.metaKey, - shiftKey: inEvent.shiftKey, - _source: 'tap' - }); - t.dispatchEvent(e); - } - } - pointermap.delete(inEvent.pointerId); - } - }; - // patch eventFactory to remove id from tap's pointermap for preventTap calls - eventFactory.preventTap = function(e) { - return function() { - e.tapPrevented = true; - pointermap.delete(e.pointerId); - }; - }; - dispatcher.registerGesture('tap', tap); -})(window.PolymerGestures); - -/* - * Basic strategy: find the farthest apart points, use as diameter of circle - * react to size change and rotation of the chord - */ - -/** - * @module pointer-gestures - * @submodule Events - * @class pinch - */ -/** - * Scale of the pinch zoom gesture - * @property scale - * @type Number - */ -/** - * Center X position of pointers causing pinch - * @property centerX - * @type Number - */ -/** - * Center Y position of pointers causing pinch - * @property centerY - * @type Number - */ - -/** - * @module pointer-gestures - * @submodule Events - * @class rotate - */ -/** - * Angle (in degrees) of rotation. Measured from starting positions of pointers. - * @property angle - * @type Number - */ -/** - * Center X position of pointers causing rotation - * @property centerX - * @type Number - */ -/** - * Center Y position of pointers causing rotation - * @property centerY - * @type Number - */ -(function(scope) { - var dispatcher = scope.dispatcher; - var eventFactory = scope.eventFactory; - var pointermap = new scope.PointerMap(); - var RAD_TO_DEG = 180 / Math.PI; - var pinch = { - events: [ - 'down', - 'up', - 'move', - 'cancel' - ], - exposes: [ - 'pinchstart', - 'pinch', - 'pinchend', - 'rotate' - ], - defaultActions: { - 'pinch': 'none', - 'rotate': 'none' - }, - reference: {}, - down: function(inEvent) { - pointermap.set(inEvent.pointerId, inEvent); - if (pointermap.pointers() == 2) { - var points = this.calcChord(); - var angle = this.calcAngle(points); - this.reference = { - angle: angle, - diameter: points.diameter, - target: scope.targetFinding.LCA(points.a.target, points.b.target) - }; - - this.firePinch('pinchstart', points.diameter, points); - } - }, - up: function(inEvent) { - var p = pointermap.get(inEvent.pointerId); - var num = pointermap.pointers(); - if (p) { - if (num === 2) { - // fire 'pinchend' before deleting pointer - var points = this.calcChord(); - this.firePinch('pinchend', points.diameter, points); - } - pointermap.delete(inEvent.pointerId); - } - }, - move: function(inEvent) { - if (pointermap.has(inEvent.pointerId)) { - pointermap.set(inEvent.pointerId, inEvent); - if (pointermap.pointers() > 1) { - this.calcPinchRotate(); - } - } - }, - cancel: function(inEvent) { - this.up(inEvent); - }, - firePinch: function(type, diameter, points) { - var zoom = diameter / this.reference.diameter; - var e = eventFactory.makeGestureEvent(type, { - bubbles: true, - cancelable: true, - scale: zoom, - centerX: points.center.x, - centerY: points.center.y, - _source: 'pinch' - }); - this.reference.target.dispatchEvent(e); - }, - fireRotate: function(angle, points) { - var diff = Math.round((angle - this.reference.angle) % 360); - var e = eventFactory.makeGestureEvent('rotate', { - bubbles: true, - cancelable: true, - angle: diff, - centerX: points.center.x, - centerY: points.center.y, - _source: 'pinch' - }); - this.reference.target.dispatchEvent(e); - }, - calcPinchRotate: function() { - var points = this.calcChord(); - var diameter = points.diameter; - var angle = this.calcAngle(points); - if (diameter != this.reference.diameter) { - this.firePinch('pinch', diameter, points); - } - if (angle != this.reference.angle) { - this.fireRotate(angle, points); - } - }, - calcChord: function() { - var pointers = []; - pointermap.forEach(function(p) { - pointers.push(p); - }); - var dist = 0; - // start with at least two pointers - var points = {a: pointers[0], b: pointers[1]}; - var x, y, d; - for (var i = 0; i < pointers.length; i++) { - var a = pointers[i]; - for (var j = i + 1; j < pointers.length; j++) { - var b = pointers[j]; - x = Math.abs(a.clientX - b.clientX); - y = Math.abs(a.clientY - b.clientY); - d = x + y; - if (d > dist) { - dist = d; - points = {a: a, b: b}; - } - } - } - x = Math.abs(points.a.clientX + points.b.clientX) / 2; - y = Math.abs(points.a.clientY + points.b.clientY) / 2; - points.center = { x: x, y: y }; - points.diameter = dist; - return points; - }, - calcAngle: function(points) { - var x = points.a.clientX - points.b.clientX; - var y = points.a.clientY - points.b.clientY; - return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; - } - }; - dispatcher.registerGesture('pinch', pinch); -})(window.PolymerGestures); - -(function (global) { - 'use strict'; - - var Token, - TokenName, - Syntax, - Messages, - source, - index, - length, - delegate, - lookahead, - state; - - Token = { - BooleanLiteral: 1, - EOF: 2, - Identifier: 3, - Keyword: 4, - NullLiteral: 5, - NumericLiteral: 6, - Punctuator: 7, - StringLiteral: 8 - }; - - TokenName = {}; - TokenName[Token.BooleanLiteral] = 'Boolean'; - TokenName[Token.EOF] = ''; - TokenName[Token.Identifier] = 'Identifier'; - TokenName[Token.Keyword] = 'Keyword'; - TokenName[Token.NullLiteral] = 'Null'; - TokenName[Token.NumericLiteral] = 'Numeric'; - TokenName[Token.Punctuator] = 'Punctuator'; - TokenName[Token.StringLiteral] = 'String'; - - Syntax = { - ArrayExpression: 'ArrayExpression', - BinaryExpression: 'BinaryExpression', - CallExpression: 'CallExpression', - ConditionalExpression: 'ConditionalExpression', - EmptyStatement: 'EmptyStatement', - ExpressionStatement: 'ExpressionStatement', - Identifier: 'Identifier', - Literal: 'Literal', - LabeledStatement: 'LabeledStatement', - LogicalExpression: 'LogicalExpression', - MemberExpression: 'MemberExpression', - ObjectExpression: 'ObjectExpression', - Program: 'Program', - Property: 'Property', - ThisExpression: 'ThisExpression', - UnaryExpression: 'UnaryExpression' - }; - - // Error messages should be identical to V8. - Messages = { - UnexpectedToken: 'Unexpected token %0', - UnknownLabel: 'Undefined label \'%0\'', - Redeclaration: '%0 \'%1\' has already been declared' - }; - - // Ensure the condition is true, otherwise throw an error. - // This is only to have a better contract semantic, i.e. another safety net - // to catch a logic error. The condition shall be fulfilled in normal case. - // Do NOT use this to enforce a certain condition on any user input. - - function assert(condition, message) { - if (!condition) { - throw new Error('ASSERT: ' + message); - } - } - - function isDecimalDigit(ch) { - return (ch >= 48 && ch <= 57); // 0..9 - } - - - // 7.2 White Space - - function isWhiteSpace(ch) { - return (ch === 32) || // space - (ch === 9) || // tab - (ch === 0xB) || - (ch === 0xC) || - (ch === 0xA0) || - (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0); - } - - // 7.3 Line Terminators - - function isLineTerminator(ch) { - return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); - } - - // 7.6 Identifier Names and Identifiers - - function isIdentifierStart(ch) { - return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) - (ch >= 65 && ch <= 90) || // A..Z - (ch >= 97 && ch <= 122); // a..z - } - - function isIdentifierPart(ch) { - return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) - (ch >= 65 && ch <= 90) || // A..Z - (ch >= 97 && ch <= 122) || // a..z - (ch >= 48 && ch <= 57); // 0..9 - } - - // 7.6.1.1 Keywords - - function isKeyword(id) { - return (id === 'this') - } - - // 7.4 Comments - - function skipWhitespace() { - while (index < length && isWhiteSpace(source.charCodeAt(index))) { - ++index; - } - } - - function getIdentifier() { - var start, ch; - - start = index++; - while (index < length) { - ch = source.charCodeAt(index); - if (isIdentifierPart(ch)) { - ++index; - } else { - break; - } - } - - return source.slice(start, index); - } - - function scanIdentifier() { - var start, id, type; - - start = index; - - id = getIdentifier(); - - // There is no keyword or literal with only one character. - // Thus, it must be an identifier. - if (id.length === 1) { - type = Token.Identifier; - } else if (isKeyword(id)) { - type = Token.Keyword; - } else if (id === 'null') { - type = Token.NullLiteral; - } else if (id === 'true' || id === 'false') { - type = Token.BooleanLiteral; - } else { - type = Token.Identifier; - } - - return { - type: type, - value: id, - range: [start, index] - }; - } - - - // 7.7 Punctuators - - function scanPunctuator() { - var start = index, - code = source.charCodeAt(index), - code2, - ch1 = source[index], - ch2; - - switch (code) { - - // Check for most common single-character punctuators. - case 46: // . dot - case 40: // ( open bracket - case 41: // ) close bracket - case 59: // ; semicolon - case 44: // , comma - case 123: // { open curly brace - case 125: // } close curly brace - case 91: // [ - case 93: // ] - case 58: // : - case 63: // ? - ++index; - return { - type: Token.Punctuator, - value: String.fromCharCode(code), - range: [start, index] - }; - - default: - code2 = source.charCodeAt(index + 1); - - // '=' (char #61) marks an assignment or comparison operator. - if (code2 === 61) { - switch (code) { - case 37: // % - case 38: // & - case 42: // *: - case 43: // + - case 45: // - - case 47: // / - case 60: // < - case 62: // > - case 124: // | - index += 2; - return { - type: Token.Punctuator, - value: String.fromCharCode(code) + String.fromCharCode(code2), - range: [start, index] - }; - - case 33: // ! - case 61: // = - index += 2; - - // !== and === - if (source.charCodeAt(index) === 61) { - ++index; - } - return { - type: Token.Punctuator, - value: source.slice(start, index), - range: [start, index] - }; - default: - break; - } - } - break; - } - - // Peek more characters. - - ch2 = source[index + 1]; - - // Other 2-character punctuators: && || - - if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { - index += 2; - return { - type: Token.Punctuator, - value: ch1 + ch2, - range: [start, index] - }; - } - - if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { - ++index; - return { - type: Token.Punctuator, - value: ch1, - range: [start, index] - }; - } - - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - // 7.8.3 Numeric Literals - function scanNumericLiteral() { - var number, start, ch; - - ch = source[index]; - assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), - 'Numeric literal must start with a decimal digit or a decimal point'); - - start = index; - number = ''; - if (ch !== '.') { - number = source[index++]; - ch = source[index]; - - // Hex number starts with '0x'. - // Octal number starts with '0'. - if (number === '0') { - // decimal number starts with '0' such as '09' is illegal. - if (ch && isDecimalDigit(ch.charCodeAt(0))) { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - } - - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - ch = source[index]; - } - - if (ch === '.') { - number += source[index++]; - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - ch = source[index]; - } - - if (ch === 'e' || ch === 'E') { - number += source[index++]; - - ch = source[index]; - if (ch === '+' || ch === '-') { - number += source[index++]; - } - if (isDecimalDigit(source.charCodeAt(index))) { - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - } else { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - } - - if (isIdentifierStart(source.charCodeAt(index))) { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - return { - type: Token.NumericLiteral, - value: parseFloat(number), - range: [start, index] - }; - } - - // 7.8.4 String Literals - - function scanStringLiteral() { - var str = '', quote, start, ch, octal = false; - - quote = source[index]; - assert((quote === '\'' || quote === '"'), - 'String literal must starts with a quote'); - - start = index; - ++index; - - while (index < length) { - ch = source[index++]; - - if (ch === quote) { - quote = ''; - break; - } else if (ch === '\\') { - ch = source[index++]; - if (!ch || !isLineTerminator(ch.charCodeAt(0))) { - switch (ch) { - case 'n': - str += '\n'; - break; - case 'r': - str += '\r'; - break; - case 't': - str += '\t'; - break; - case 'b': - str += '\b'; - break; - case 'f': - str += '\f'; - break; - case 'v': - str += '\x0B'; - break; - - default: - str += ch; - break; - } - } else { - if (ch === '\r' && source[index] === '\n') { - ++index; - } - } - } else if (isLineTerminator(ch.charCodeAt(0))) { - break; - } else { - str += ch; - } - } - - if (quote !== '') { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - return { - type: Token.StringLiteral, - value: str, - octal: octal, - range: [start, index] - }; - } - - function isIdentifierName(token) { - return token.type === Token.Identifier || - token.type === Token.Keyword || - token.type === Token.BooleanLiteral || - token.type === Token.NullLiteral; - } - - function advance() { - var ch; - - skipWhitespace(); - - if (index >= length) { - return { - type: Token.EOF, - range: [index, index] - }; - } - - ch = source.charCodeAt(index); - - // Very common: ( and ) and ; - if (ch === 40 || ch === 41 || ch === 58) { - return scanPunctuator(); - } - - // String literal starts with single quote (#39) or double quote (#34). - if (ch === 39 || ch === 34) { - return scanStringLiteral(); - } - - if (isIdentifierStart(ch)) { - return scanIdentifier(); - } - - // Dot (.) char #46 can also start a floating-point number, hence the need - // to check the next character. - if (ch === 46) { - if (isDecimalDigit(source.charCodeAt(index + 1))) { - return scanNumericLiteral(); - } - return scanPunctuator(); - } - - if (isDecimalDigit(ch)) { - return scanNumericLiteral(); - } - - return scanPunctuator(); - } - - function lex() { - var token; - - token = lookahead; - index = token.range[1]; - - lookahead = advance(); - - index = token.range[1]; - - return token; - } - - function peek() { - var pos; - - pos = index; - lookahead = advance(); - index = pos; - } - - // Throw an exception - - function throwError(token, messageFormat) { - var error, - args = Array.prototype.slice.call(arguments, 2), - msg = messageFormat.replace( - /%(\d)/g, - function (whole, index) { - assert(index < args.length, 'Message reference must be in range'); - return args[index]; - } - ); - - error = new Error(msg); - error.index = index; - error.description = msg; - throw error; - } - - // Throw an exception because of the token. - - function throwUnexpected(token) { - throwError(token, Messages.UnexpectedToken, token.value); - } - - // Expect the next token to match the specified punctuator. - // If not, an exception will be thrown. - - function expect(value) { - var token = lex(); - if (token.type !== Token.Punctuator || token.value !== value) { - throwUnexpected(token); - } - } - - // Return true if the next token matches the specified punctuator. - - function match(value) { - return lookahead.type === Token.Punctuator && lookahead.value === value; - } - - // Return true if the next token matches the specified keyword - - function matchKeyword(keyword) { - return lookahead.type === Token.Keyword && lookahead.value === keyword; - } - - function consumeSemicolon() { - // Catch the very common case first: immediately a semicolon (char #59). - if (source.charCodeAt(index) === 59) { - lex(); - return; - } - - skipWhitespace(); - - if (match(';')) { - lex(); - return; - } - - if (lookahead.type !== Token.EOF && !match('}')) { - throwUnexpected(lookahead); - } - } - - // 11.1.4 Array Initialiser - - function parseArrayInitialiser() { - var elements = []; - - expect('['); - - while (!match(']')) { - if (match(',')) { - lex(); - elements.push(null); - } else { - elements.push(parseExpression()); - - if (!match(']')) { - expect(','); - } - } - } - - expect(']'); - - return delegate.createArrayExpression(elements); - } - - // 11.1.5 Object Initialiser - - function parseObjectPropertyKey() { - var token; - - skipWhitespace(); - token = lex(); - - // Note: This function is called only from parseObjectProperty(), where - // EOF and Punctuator tokens are already filtered out. - if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { - return delegate.createLiteral(token); - } - - return delegate.createIdentifier(token.value); - } - - function parseObjectProperty() { - var token, key; - - token = lookahead; - skipWhitespace(); - - if (token.type === Token.EOF || token.type === Token.Punctuator) { - throwUnexpected(token); - } - - key = parseObjectPropertyKey(); - expect(':'); - return delegate.createProperty('init', key, parseExpression()); - } - - function parseObjectInitialiser() { - var properties = []; - - expect('{'); - - while (!match('}')) { - properties.push(parseObjectProperty()); - - if (!match('}')) { - expect(','); - } - } - - expect('}'); - - return delegate.createObjectExpression(properties); - } - - // 11.1.6 The Grouping Operator - - function parseGroupExpression() { - var expr; - - expect('('); - - expr = parseExpression(); - - expect(')'); - - return expr; - } - - - // 11.1 Primary Expressions - - function parsePrimaryExpression() { - var type, token, expr; - - if (match('(')) { - return parseGroupExpression(); - } - - type = lookahead.type; - - if (type === Token.Identifier) { - expr = delegate.createIdentifier(lex().value); - } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { - expr = delegate.createLiteral(lex()); - } else if (type === Token.Keyword) { - if (matchKeyword('this')) { - lex(); - expr = delegate.createThisExpression(); - } - } else if (type === Token.BooleanLiteral) { - token = lex(); - token.value = (token.value === 'true'); - expr = delegate.createLiteral(token); - } else if (type === Token.NullLiteral) { - token = lex(); - token.value = null; - expr = delegate.createLiteral(token); - } else if (match('[')) { - expr = parseArrayInitialiser(); - } else if (match('{')) { - expr = parseObjectInitialiser(); - } - - if (expr) { - return expr; - } - - throwUnexpected(lex()); - } - - // 11.2 Left-Hand-Side Expressions - - function parseArguments() { - var args = []; - - expect('('); - - if (!match(')')) { - while (index < length) { - args.push(parseExpression()); - if (match(')')) { - break; - } - expect(','); - } - } - - expect(')'); - - return args; - } - - function parseNonComputedProperty() { - var token; - - token = lex(); - - if (!isIdentifierName(token)) { - throwUnexpected(token); - } - - return delegate.createIdentifier(token.value); - } - - function parseNonComputedMember() { - expect('.'); - - return parseNonComputedProperty(); - } - - function parseComputedMember() { - var expr; - - expect('['); - - expr = parseExpression(); - - expect(']'); - - return expr; - } - - function parseLeftHandSideExpression() { - var expr, args, property; - - expr = parsePrimaryExpression(); - - while (true) { - if (match('[')) { - property = parseComputedMember(); - expr = delegate.createMemberExpression('[', expr, property); - } else if (match('.')) { - property = parseNonComputedMember(); - expr = delegate.createMemberExpression('.', expr, property); - } else if (match('(')) { - args = parseArguments(); - expr = delegate.createCallExpression(expr, args); - } else { - break; - } - } - - return expr; - } - - // 11.3 Postfix Expressions - - var parsePostfixExpression = parseLeftHandSideExpression; - - // 11.4 Unary Operators - - function parseUnaryExpression() { - var token, expr; - - if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { - expr = parsePostfixExpression(); - } else if (match('+') || match('-') || match('!')) { - token = lex(); - expr = parseUnaryExpression(); - expr = delegate.createUnaryExpression(token.value, expr); - } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { - throwError({}, Messages.UnexpectedToken); - } else { - expr = parsePostfixExpression(); - } - - return expr; - } - - function binaryPrecedence(token) { - var prec = 0; - - if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { - return 0; - } - - switch (token.value) { - case '||': - prec = 1; - break; - - case '&&': - prec = 2; - break; - - case '==': - case '!=': - case '===': - case '!==': - prec = 6; - break; - - case '<': - case '>': - case '<=': - case '>=': - case 'instanceof': - prec = 7; - break; - - case 'in': - prec = 7; - break; - - case '+': - case '-': - prec = 9; - break; - - case '*': - case '/': - case '%': - prec = 11; - break; - - default: - break; - } - - return prec; - } - - // 11.5 Multiplicative Operators - // 11.6 Additive Operators - // 11.7 Bitwise Shift Operators - // 11.8 Relational Operators - // 11.9 Equality Operators - // 11.10 Binary Bitwise Operators - // 11.11 Binary Logical Operators - - function parseBinaryExpression() { - var expr, token, prec, stack, right, operator, left, i; - - left = parseUnaryExpression(); - - token = lookahead; - prec = binaryPrecedence(token); - if (prec === 0) { - return left; - } - token.prec = prec; - lex(); - - right = parseUnaryExpression(); - - stack = [left, token, right]; - - while ((prec = binaryPrecedence(lookahead)) > 0) { - - // Reduce: make a binary expression from the three topmost entries. - while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { - right = stack.pop(); - operator = stack.pop().value; - left = stack.pop(); - expr = delegate.createBinaryExpression(operator, left, right); - stack.push(expr); - } - - // Shift. - token = lex(); - token.prec = prec; - stack.push(token); - expr = parseUnaryExpression(); - stack.push(expr); - } - - // Final reduce to clean-up the stack. - i = stack.length - 1; - expr = stack[i]; - while (i > 1) { - expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); - i -= 2; - } - - return expr; - } - - - // 11.12 Conditional Operator - - function parseConditionalExpression() { - var expr, consequent, alternate; - - expr = parseBinaryExpression(); - - if (match('?')) { - lex(); - consequent = parseConditionalExpression(); - expect(':'); - alternate = parseConditionalExpression(); - - expr = delegate.createConditionalExpression(expr, consequent, alternate); - } - - return expr; - } - - // Simplification since we do not support AssignmentExpression. - var parseExpression = parseConditionalExpression; - - // Polymer Syntax extensions - - // Filter :: - // Identifier - // Identifier "(" ")" - // Identifier "(" FilterArguments ")" - - function parseFilter() { - var identifier, args; - - identifier = lex(); - - if (identifier.type !== Token.Identifier) { - throwUnexpected(identifier); - } - - args = match('(') ? parseArguments() : []; - - return delegate.createFilter(identifier.value, args); - } - - // Filters :: - // "|" Filter - // Filters "|" Filter - - function parseFilters() { - while (match('|')) { - lex(); - parseFilter(); - } - } - - // TopLevel :: - // LabelledExpressions - // AsExpression - // InExpression - // FilterExpression - - // AsExpression :: - // FilterExpression as Identifier - - // InExpression :: - // Identifier, Identifier in FilterExpression - // Identifier in FilterExpression - - // FilterExpression :: - // Expression - // Expression Filters - - function parseTopLevel() { - skipWhitespace(); - peek(); - - var expr = parseExpression(); - if (expr) { - if (lookahead.value === ',' || lookahead.value == 'in' && - expr.type === Syntax.Identifier) { - parseInExpression(expr); - } else { - parseFilters(); - if (lookahead.value === 'as') { - parseAsExpression(expr); - } else { - delegate.createTopLevel(expr); - } - } - } - - if (lookahead.type !== Token.EOF) { - throwUnexpected(lookahead); - } - } - - function parseAsExpression(expr) { - lex(); // as - var identifier = lex().value; - delegate.createAsExpression(expr, identifier); - } - - function parseInExpression(identifier) { - var indexName; - if (lookahead.value === ',') { - lex(); - if (lookahead.type !== Token.Identifier) - throwUnexpected(lookahead); - indexName = lex().value; - } - - lex(); // in - var expr = parseExpression(); - parseFilters(); - delegate.createInExpression(identifier.name, indexName, expr); - } - - function parse(code, inDelegate) { - delegate = inDelegate; - source = code; - index = 0; - length = source.length; - lookahead = null; - state = { - labelSet: {} - }; - - return parseTopLevel(); - } - - global.esprima = { - parse: parse - }; -})(this); - -// Copyright (c) 2014 The Polymer Project Authors. All rights reserved. -// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt -// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt -// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt -// Code distributed by Google as part of the polymer project is also -// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - -(function (global) { - 'use strict'; - - function prepareBinding(expressionText, name, node, filterRegistry) { - var expression; - try { - expression = getExpression(expressionText); - if (expression.scopeIdent && - (node.nodeType !== Node.ELEMENT_NODE || - node.tagName !== 'TEMPLATE' || - (name !== 'bind' && name !== 'repeat'))) { - throw Error('as and in can only be used within