From 877365d00c3fc06c52b145f77fb687a7935f2f22 Mon Sep 17 00:00:00 2001 From: DadaMonad Date: Fri, 2 Jan 2015 22:43:43 +0000 Subject: [PATCH] adding yatta-element --- .../core-component-page/README.md | 6 + .../core-component-page/bowager-logo.png | Bin 0 -> 5165 bytes .../core-component-page/bower.json | 9 + .../core-component-page.html | 37 + .../core-component-page/demo.html | 23 + .../core-component-page/index.html | 22 + .../bower_components/peerjs/README.md | 39 + .../bower_components/peerjs/bower.json | 24 + .../connector/bower_components/peerjs/peer.js | 2939 ++++ .../bower_components/peerjs/peer.min.js | 2 + .../bower_components/polymer/README.md | 21 + .../bower_components/polymer/bower.json | 23 + .../bower_components/polymer/build.log | 25 + .../bower_components/polymer/layout.html | 286 + .../bower_components/polymer/polymer.html | 12 + .../bower_components/polymer/polymer.js | 11851 ++++++++++++++++ .../bower_components/polymer/polymer.min.js | 14 + .../webcomponentsjs/CustomElements.js | 932 ++ .../webcomponentsjs/CustomElements.min.js | 11 + .../webcomponentsjs/HTMLImports.js | 1055 ++ .../webcomponentsjs/HTMLImports.min.js | 11 + .../webcomponentsjs/README.md | 44 + .../webcomponentsjs/ShadowDOM.js | 4248 ++++++ .../webcomponentsjs/ShadowDOM.min.js | 13 + .../webcomponentsjs/bower.json | 14 + .../webcomponentsjs/build.log | 31 + .../webcomponentsjs/package.json | 31 + .../webcomponentsjs/webcomponents-lite.js | 1719 +++ .../webcomponentsjs/webcomponents-lite.min.js | 11 + .../webcomponentsjs/webcomponents.js | 6375 +++++++++ .../webcomponentsjs/webcomponents.min.js | 14 + build/browser/yatta-element.js | 1983 +++ examples/Polymer/index.html | 15 + examples/Polymer/index.js | 18 + examples/Polymer/yatta-test.html | 26 + lib/yatta-element.coffee | 52 + lib/yatta.coffee | 41 + yatta-element.html | 6 + yatta-element.js | 1 + 39 files changed, 31984 insertions(+) create mode 100644 bower_components/connector/bower_components/core-component-page/README.md create mode 100644 bower_components/connector/bower_components/core-component-page/bowager-logo.png create mode 100644 bower_components/connector/bower_components/core-component-page/bower.json create mode 100644 bower_components/connector/bower_components/core-component-page/core-component-page.html create mode 100644 bower_components/connector/bower_components/core-component-page/demo.html create mode 100644 bower_components/connector/bower_components/core-component-page/index.html create mode 100644 bower_components/connector/bower_components/peerjs/README.md create mode 100644 bower_components/connector/bower_components/peerjs/bower.json create mode 100644 bower_components/connector/bower_components/peerjs/peer.js create mode 100644 bower_components/connector/bower_components/peerjs/peer.min.js create mode 100644 bower_components/connector/bower_components/polymer/README.md create mode 100644 bower_components/connector/bower_components/polymer/bower.json create mode 100644 bower_components/connector/bower_components/polymer/build.log create mode 100644 bower_components/connector/bower_components/polymer/layout.html create mode 100644 bower_components/connector/bower_components/polymer/polymer.html create mode 100644 bower_components/connector/bower_components/polymer/polymer.js create mode 100644 bower_components/connector/bower_components/polymer/polymer.min.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/CustomElements.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/CustomElements.min.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/HTMLImports.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/HTMLImports.min.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/README.md create mode 100644 bower_components/connector/bower_components/webcomponentsjs/ShadowDOM.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/ShadowDOM.min.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/bower.json create mode 100644 bower_components/connector/bower_components/webcomponentsjs/build.log create mode 100644 bower_components/connector/bower_components/webcomponentsjs/package.json create mode 100644 bower_components/connector/bower_components/webcomponentsjs/webcomponents-lite.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/webcomponents-lite.min.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/webcomponents.js create mode 100644 bower_components/connector/bower_components/webcomponentsjs/webcomponents.min.js create mode 100644 build/browser/yatta-element.js create mode 100644 examples/Polymer/index.html create mode 100644 examples/Polymer/index.js create mode 100644 examples/Polymer/yatta-test.html create mode 100644 lib/yatta-element.coffee create mode 100644 lib/yatta.coffee create mode 100644 yatta-element.html create mode 100644 yatta-element.js diff --git a/bower_components/connector/bower_components/core-component-page/README.md b/bower_components/connector/bower_components/core-component-page/README.md new file mode 100644 index 00000000..7cb18ec7 --- /dev/null +++ b/bower_components/connector/bower_components/core-component-page/README.md @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..76be9fb04c197df1ecf6059221f6155516997939 GIT binary patch literal 5165 zcmV+|6w>R7P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000SCNklW^8qU>E8NqO=Sjj^O8|60 zkfaBIZtVXQ0C^GlB!F;`JDH!KpA(VO{&QD{h@76EpP%!e`?S!lmA@T=b3YT_&k0s4 zl_Mmd1aL}3Vv>JcU0wa%i4!NJ%lX|6-~!2yT5A(;{x^3*DJZ1^5qTNFACyvWbz9KV z($beiGszL12!L|AJg2pO62K{IZNj~#0n`CJv%0!^Woc>Y6966& zkrDt4z#3zEE~>Srvr0vB1Hf~utE+!mT3Wgnz!Lz9B4R{Dlu`kJYsQ$1YinyarVcGX~&r9e^v&;Xhwb0T6z#5zkDd?w#@h>D~Vk(M)eBO*4vjsxMUG3Mg>`g%0Y zFi54;M*!Sqtu@^aP4-#>VAfh|WKVLY$Mx%eoO{Lqm=%%4&(Sr`0JzJ^GM3{UHw-dK zlGlSEXp@`)(0$%-89UAb!^}aFBGOKh z5Rn(__4@U6G&yJhJj$}Rw)Q-LEt1Ng-KoUxcm2$klR>&%-Q8!M=O23K zlv31P&3`}aJS6q*?(VYyjFV-o^7bSE7ywDVUVjz9=6)bJr4@jHWQOE6fVTj=MRJ>D z20*Zfh1yqn0Gn>@#K|Jlbb{hR2*dCJ0LPuz9~9cO1N_g;X7i;eiZ)V0vrs6Ma=F}t z06s<1Dy2-nkSQXTszm$?^s!Wgof=Z?G6A?L{j?q0Bo)(dnX0!Q=D2moo zJ0_{Q6aY0pKYwO+cJ?unCtQf_>jOw$uU4zya#kQyPlwCp^4%g54pJU~7Lk=kqw#bU zMO6T8*W(_&y|uOVW~0$~T0~Yz>R!&(S_?<6!g9HM_xq9z6^q3J$%h>B{m!Z)shiE_ z&swe4R!aFcfKFGx`CF^i+G@AkzXqV4x9{fm)6$Se&dwf_2yrxRBm~uTCIAeG=LcZnR2=O6#x$c=y?>h)^v1Pk$fA# zzg@rVlcfFh3x+iHIcx3DN#;dFNQx6moM8PNg^uI+1*iBf08^<{Y@e)B07$i3y@`Ds z$1h~F*-yJL_nu~qfl|uu5kk33M9z@>f`~K$JT{DD9Oekn7^46jal2=XG1eF(gAyA@ zj_Qp@qv}IdKjpiCus*~#8jY$_s_u?KU&?HZ5o3&h&Nx}sFwAfm3t+uokHave17p}y z8AYU?5=urWpLw2PthIFj3nH?wGR7Eat?9<n?3rkvSt(*rQl+IdfZE0a)AsjbEEi5dA*4mOC z;A}`jYfY`SauiuRS#K*9mYtoQZ)daF4{EK?0r-H3oKi{+EO7ylnVFdj0Dd`6h@P35 zx!@cJ2bIuV7`z7H^(0ANi{toT!&==iJU}_1GA=%A04kf!9w`(GUv@=K(`z2*kOC3; z`o_jabyz=8tJN5W;XMF;Ofu;OFiB6bUyh>aw{aZr_zcyB+zx=mm*OTl0@5{U8$c(H z<3^{`xs-A?y~$YBSDjAhQXI#Pv}|d@bvt~3_r^&V zk8f{pZ@)qEy4owx^h`ut757XK1Q$i*o*}~LJ#OroewO8SUy;1Ny}kWLS|-<%VIznd z!6W;4o}{c{H%XG;yJOLFmqditS}P)F0XzlZaS^HXP`m=*aR5(=$XWNypt^u|YwxCZ zJxTM9gHDG%XP*tj@EMYs0nX6x8lA%;$#np0o^89VQb>8!7>KjC)VoBYV> z@FbU`7Jw}li|0t@t+mOZ*efD1#=s>|P)d~mlu}{Sq^iN(uVSo}$`^~pbM<=tpZ#)$ z15Jm$UO^CiZ4?2VUx3ReZg&6uC>!9`p6FHANdREETz*tULT9p5D~UV5BCa6v=(u+e z6Wt7zN~Ma3JPg3TAMYExHLZv|Tq>0+nD)((QtEyHId^v1%W*2Dl&7(yf;7w=&KKlJ z-VdNU&3ggHar_F&t4b-IjubvdE2RRGn@TC|dBQM>lpp8j&~A>Pn`?L`Nb+hN$FGcQ z0po;0pB`uO`TWA%+}!uEcRg-gJztT$-syCHwYj)YZyh~)^c?`FdhC`W`A!_iTPa$bwg3$vh@xl(z)EWU-AO7(lQKc11W+kp zx^I4zA1@+T0GuXyb9da|1aKwA?M-S8rmV_mpEfmSY$iDAdxAElG}EQ5uH4_$YBc~z zthHCP)}QZDPNFg9inpG{#lOy6v>v?aTelIaMe{dMyX+_;hyC*W^Z7%bnv{S6vEHJjB-B2FI%M zIz@Y#vwvq;ZkO^?IRaz&jHC+)bU9BuO@XEjfOLhnUKTRd%!-HqIeLe;LjVo|I0WF% bZvPnoTic+%?!_>300000NkvXXu0mjfnNqLt literal 0 HcmV?d00001 diff --git a/bower_components/connector/bower_components/core-component-page/bower.json b/bower_components/connector/bower_components/core-component-page/bower.json new file mode 100644 index 00000000..9d3bb7e7 --- /dev/null +++ b/bower_components/connector/bower_components/core-component-page/bower.json @@ -0,0 +1,9 @@ +{ + "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 new file mode 100644 index 00000000..5c642c77 --- /dev/null +++ b/bower_components/connector/bower_components/core-component-page/core-component-page.html @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/bower_components/connector/bower_components/core-component-page/demo.html b/bower_components/connector/bower_components/core-component-page/demo.html new file mode 100644 index 00000000..3c414d85 --- /dev/null +++ b/bower_components/connector/bower_components/core-component-page/demo.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/bower_components/connector/bower_components/core-component-page/index.html b/bower_components/connector/bower_components/core-component-page/index.html new file mode 100644 index 00000000..294215a7 --- /dev/null +++ b/bower_components/connector/bower_components/core-component-page/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/bower_components/connector/bower_components/peerjs/README.md b/bower_components/connector/bower_components/peerjs/README.md new file mode 100644 index 00000000..1283b59c --- /dev/null +++ b/bower_components/connector/bower_components/peerjs/README.md @@ -0,0 +1,39 @@ +# 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 new file mode 100644 index 00000000..2816a850 --- /dev/null +++ b/bower_components/connector/bower_components/peerjs/bower.json @@ -0,0 +1,24 @@ +{ + "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 new file mode 100644 index 00000000..bad01c9e --- /dev/null +++ b/bower_components/connector/bower_components/peerjs/peer.js @@ -0,0 +1,2939 @@ +/*! 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 new file mode 100644 index 00000000..0aa06e93 --- /dev/null +++ b/bower_components/connector/bower_components/peerjs/peer.min.js @@ -0,0 +1,2 @@ +/*! 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 new file mode 100644 index 00000000..e72f5bea --- /dev/null +++ b/bower_components/connector/bower_components/polymer/build.log @@ -0,0 +1,25 @@ +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 new file mode 100644 index 00000000..55d4d2f0 --- /dev/null +++ b/bower_components/connector/bower_components/polymer/layout.html @@ -0,0 +1,286 @@ + + \ 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 new file mode 100644 index 00000000..7e3d8f1e --- /dev/null +++ b/bower_components/connector/bower_components/polymer/polymer.html @@ -0,0 +1,12 @@ + + + + + diff --git a/bower_components/connector/bower_components/polymer/polymer.js b/bower_components/connector/bower_components/polymer/polymer.js new file mode 100644 index 00000000..2d3b692c --- /dev/null +++ b/bower_components/connector/bower_components/polymer/polymer.js @@ -0,0 +1,11851 @@ +/** + * @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