├── .bowerrc ├── .gitignore ├── .nodemonignore ├── .tmp ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── Gruntfile.coffee ├── Gruntfile.js ├── INSTALL.md ├── LICENSE ├── README.md ├── assets ├── echoplexus-logo.png └── echoplexus-logo.psd ├── bower.json ├── lib ├── CryptoJS-3.1.2 │ ├── components │ │ ├── aes-min.js │ │ ├── aes.js │ │ ├── cipher-core-min.js │ │ ├── cipher-core.js │ │ ├── core-min.js │ │ ├── core.js │ │ ├── enc-base64-min.js │ │ ├── enc-base64.js │ │ ├── enc-utf16-min.js │ │ ├── enc-utf16.js │ │ ├── evpkdf-min.js │ │ ├── evpkdf.js │ │ ├── format-hex-min.js │ │ ├── format-hex.js │ │ ├── hmac-min.js │ │ ├── hmac.js │ │ ├── lib-typedarrays-min.js │ │ ├── lib-typedarrays.js │ │ ├── md5-min.js │ │ ├── md5.js │ │ ├── mode-cfb-min.js │ │ ├── mode-cfb.js │ │ ├── mode-ctr-gladman-min.js │ │ ├── mode-ctr-gladman.js │ │ ├── mode-ctr-min.js │ │ ├── mode-ctr.js │ │ ├── mode-ecb-min.js │ │ ├── mode-ecb.js │ │ ├── mode-ofb-min.js │ │ ├── mode-ofb.js │ │ ├── pad-ansix923-min.js │ │ ├── pad-ansix923.js │ │ ├── pad-iso10126-min.js │ │ ├── pad-iso10126.js │ │ ├── pad-iso97971-min.js │ │ ├── pad-iso97971.js │ │ ├── pad-nopadding-min.js │ │ ├── pad-nopadding.js │ │ ├── pad-zeropadding-min.js │ │ ├── pad-zeropadding.js │ │ ├── pbkdf2-min.js │ │ ├── pbkdf2.js │ │ ├── rabbit-legacy-min.js │ │ ├── rabbit-legacy.js │ │ ├── rabbit-min.js │ │ ├── rabbit.js │ │ ├── rc4-min.js │ │ ├── rc4.js │ │ ├── ripemd160-min.js │ │ ├── ripemd160.js │ │ ├── sha1-min.js │ │ ├── sha1.js │ │ ├── sha224-min.js │ │ ├── sha224.js │ │ ├── sha256-min.js │ │ ├── sha256.js │ │ ├── sha3-min.js │ │ ├── sha3.js │ │ ├── sha384-min.js │ │ ├── sha384.js │ │ ├── sha512-min.js │ │ ├── sha512.js │ │ ├── tripledes-min.js │ │ ├── tripledes.js │ │ ├── x64-core-min.js │ │ └── x64-core.js │ └── rollups │ │ ├── aes.js │ │ ├── hmac-md5.js │ │ ├── hmac-ripemd160.js │ │ ├── hmac-sha1.js │ │ ├── hmac-sha224.js │ │ ├── hmac-sha256.js │ │ ├── hmac-sha3.js │ │ ├── hmac-sha384.js │ │ ├── hmac-sha512.js │ │ ├── md5.js │ │ ├── pbkdf2.js │ │ ├── rabbit-legacy.js │ │ ├── rabbit.js │ │ ├── rc4.js │ │ ├── ripemd160.js │ │ ├── sha1.js │ │ ├── sha224.js │ │ ├── sha256.js │ │ ├── sha3.js │ │ ├── sha384.js │ │ ├── sha512.js │ │ └── tripledes.js └── openpgpjs │ ├── openpgp.min.js │ └── openpgp.worker.min.js ├── package.json ├── public ├── .gitignore ├── beep-xylo.mp3 ├── codeframe.html ├── echoplexus-logo-120.png ├── echoplexus-logo-128.png ├── echoplexus-logo-16.png ├── echoplexus-logo-30.png ├── echoplexus-logo-32.png ├── echoplexus-logo-48.png ├── echoplexus-logo-60.png ├── echoplexus-logo-64.png ├── echoplexus-logo-90.png ├── echoplexus-logo.png ├── embed.html ├── embedded-test.html ├── favicon-activity.png ├── favicon-disconnected.png ├── favicon.png ├── index.dev.html ├── install.html ├── manifest.webapp ├── mobile.html ├── noisy_grid.png └── noisy_net.png ├── sass ├── _animation.scss ├── _keyframes.scss ├── _prefixer.scss ├── _transform.scss ├── _transition.scss ├── call.scss ├── code.scss ├── codemirror.scss ├── combined.scss ├── drag-staging.scss ├── draw.scss ├── embedded.scss ├── left-sidenav.scss ├── main.scss ├── mobile.scss ├── modal.scss ├── monokai.scss ├── support-bar.scss ├── tooltips.scss ├── ui │ ├── mewl.scss │ ├── spectrum-overrides.scss │ ├── spectrum.scss │ └── spinner.scss └── view_modifiers.scss ├── src ├── client │ ├── CryptoWrapper.coffee │ ├── PermissionModel.coffee │ ├── bootstrap.core.js.coffee │ ├── bootstrap.desktop.js.coffee │ ├── client.js.coffee │ ├── config.coffee │ ├── events.js.coffee │ ├── keystore.js.coffee │ ├── loader.js.coffee │ ├── main.js.coffee │ ├── modules │ │ ├── call │ │ │ ├── client.js.coffee │ │ │ ├── rtc.js.coffee │ │ │ └── templates │ │ │ │ ├── callPanel.html │ │ │ │ └── mediaStreamContainer.html │ │ ├── chat │ │ │ ├── Autocomplete.js.coffee │ │ │ ├── ChatAreaView.js.coffee │ │ │ ├── ChatMessageModel.js.coffee │ │ │ ├── ChatMessageView.js.coffee │ │ │ ├── Log.js.coffee │ │ │ ├── MediaLog.js.coffee │ │ │ ├── Scrollback.js.coffee │ │ │ ├── client.js.coffee │ │ │ ├── pgp_modal.js.coffee │ │ │ ├── pgp_settings.js.coffee │ │ │ └── templates │ │ │ │ ├── channelCryptokeyModal.html │ │ │ │ ├── chatArea.html │ │ │ │ ├── chatInput.html │ │ │ │ ├── chatMessage.html │ │ │ │ ├── chatPanel.html │ │ │ │ ├── fileUpload.html │ │ │ │ ├── linkedImage.html │ │ │ │ ├── mediaLog.html │ │ │ │ ├── pgpModalTemplate.html │ │ │ │ ├── pgpPassphraseModal.html │ │ │ │ ├── userListUser.html │ │ │ │ ├── webshotBadge.html │ │ │ │ ├── webshotMediaItem.html │ │ │ │ └── youtube.html │ │ ├── code │ │ │ ├── SyncedEditor.js.coffee │ │ │ ├── client.js.coffee │ │ │ └── templates │ │ │ │ └── jsCodeRepl.html │ │ ├── draw │ │ │ ├── client.js.coffee │ │ │ └── templates │ │ │ │ └── drawing.html │ │ └── info │ │ │ └── client.js.coffee │ ├── options.js.coffee │ ├── regex.js.coffee │ ├── templates │ │ ├── MewlNotification.html │ │ ├── channelSelector.html │ │ └── channelSelectorButton.html │ ├── ui │ │ ├── ChannelSwitcher.js.coffee │ │ ├── Faviconizer.js.coffee │ │ ├── Mewl.js.coffee │ │ └── Notifications.js.coffee │ ├── utility.js.coffee │ ├── version.js.coffee │ └── visibility.js.coffee ├── embedded-client │ └── main.js.coffee ├── mobile │ └── ui │ │ └── gestures.js.coffee └── server │ ├── AbstractServer.coffee │ ├── CallServer.coffee │ ├── Channels.js.coffee │ ├── ChatServer.coffee │ ├── CodeServer.coffee │ ├── DrawServer.coffee │ ├── Error.js.coffee │ ├── EventBus.coffee │ ├── GithubWebhookIntegration.coffee │ ├── InfoServer.coffee │ ├── IrcProxyServer.coffee │ ├── PermissionModel.coffee │ ├── PhantomJS-Screenshot.js.coffee │ ├── RedisClient.coffee │ ├── client.js.coffee │ ├── config.sample.coffee │ ├── extensions │ └── dice.coffee │ ├── main.coffee │ ├── sample-gh-payload.json │ ├── samples │ ├── build_nginx.sh │ ├── echoplexus.site │ └── nginx.conf.excerpt │ ├── utility.coffee │ └── version.coffee ├── testem.json └── tests ├── client ├── client_model_test.coffee ├── color_model_test.coffee ├── htmlsanitizer_test.coffee ├── modules │ ├── call │ │ └── call_client_test.coffee │ └── chat │ │ ├── chat_client_test.coffee │ │ ├── chat_messsage_model_test.coffee │ │ └── log_test.coffee ├── regex_test.coffee └── utility_test.coffee ├── server └── extensions │ └── dice_test.coffee ├── setup.js └── tests.coffee /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "/vendor/", 3 | "json" : "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/images 3 | .sass-cache 4 | *.apk 5 | public/css 6 | public/fonts 7 | config.js 8 | app.min.js 9 | vendor.min.js 10 | vendor.js 11 | src/server/config.coffee 12 | public/sandbox/* 13 | public/js/* 14 | *.sublime-* 15 | client/lib/* 16 | src/client/lib/* 17 | Vagrantfile 18 | .vagrant 19 | npm-debug.log 20 | .DS_Store 21 | nw-build/ 22 | app.nw 23 | nw/ 24 | build/ 25 | vendor 26 | browserified.js 27 | -------------------------------------------------------------------------------- /.nodemonignore: -------------------------------------------------------------------------------- 1 | ./server/public/*.js 2 | -------------------------------------------------------------------------------- /.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/.tmp -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - gem install sass 6 | before_script: 7 | - npm install -g grunt grunt-cli testem coffee-script 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.2.6 2 | ===== 3 | 4 | Major: 5 | - addition of an IRC proxy server 6 | - Echoplexus will bridge a connection to the irc network&channel of your choice (so beware if you assumed you were connecting directly to said network!) 7 | - To join a network, join a channel name like: `irc:chat.freenode.net#foo` 8 | - Unfortunately, IRC support in echoplexus is not yet multiplexed so each channel you connect to will be its own connection and thus requires a unique nick 9 | - This feature must be enabled in server config.coffee; it's not necessarily a feature that every operator would want, so it's opt-in 10 | 11 | Minor: 12 | - "Zzz" for idle users in the user list has been removed in favour of simply colouring the active users a blue colour 13 | - Client fields are now whitelisted when userlist data is transmitted, ensuring that we only transmit the need-to-know information 14 | - New users users will now appear with a random human first name, rather than "Anonymous" 15 | - `/pseudonym` command added, which will give you a new random human name 16 | - fixed the firefox channel close button bug 17 | - various fixes for emoji related bugs (using fork of emojify.js) 18 | - adjusted styles 19 | 20 | 0.2.5 21 | ===== 22 | 23 | Major: 24 | - new UI, more accessible and appealing to more people (I hope) 25 | 26 | 0.2.4 27 | ===== 28 | 29 | Major: 30 | - Addition of PGP for identification, for more information [read the blog post](https://blog.echoplex.us/2014/03/05/echoplexus-and-pgp/) 31 | - Removal of `/identify` and `/register` for identification 32 | - Local rendering of chat messages: before your messages are even send out to the world, your client will render them locally (with a spinner), replacing them when they echo back to you. This has the effect of a net increase in speediness, as well as letting you know when your messages aren't delivered in failure situations (never stops spinning) 33 | - Chat and Media Log rendering algorithm changes to ensure that, no matter what, things are inserted in the right order 34 | - More Backbone.Stickit bindings for ChatMessage and MediaLog, allowing us to automatically decrypt the entire chat log after supplying a symmetric key (previously, you had to reload) and allowing us to decrypt all PGP encrypted chat messages as soon as you unlock your PGP keypair 35 | - The 'a few seconds ago', '10 minutes ago' timestamps are now accurate and auto-update every minute 36 | - Tests for various combinations and permutations of symmetric encryption & PGP usage, ensuring messages are routed to who we think they are 37 | 38 | Broken: 39 | - Temporarily, preferred timestamps settings will not work 40 | 41 | 0.2.3 42 | ===== 43 | 44 | Major: 45 | - Complete rewrite in coffeescript 46 | - Unit tests with `testem` 47 | - Re-tooled and remove `requirejs` in favour of `browserify` 48 | - GitHub postreceive hook support, displaying the commit names & links to the commits 49 | - Firefox Marketplace App that can [be installed here](https://chat.echoplex.us/install.html) or [on the Firefox Marketplace](https://marketplace.firefox.com/app/echoplexus), creating a usable mobile app for Firefox OS, Android via 'Firefox Beta for Android', and desktop clients via Firefox Aurora/Nightly. If you're a server operator, you can install your own copy by visiting https://yourechoplex.us/install.html 50 | - Mobile styles and touch gesture support 51 | - Automated unit tests via travis 52 | - no longer hear yourself talking when in a call 53 | - fontawesome 4.0.3 54 | - ability to pin the Chat panel in an open state 55 | 56 | Minor: 57 | - improved subdivision algorithm of Call panel, proved correctness with unit tests 58 | - miscelaneous other bug fixes uncovered by unit tests 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # qq99/echoplexus-dev docker file 2 | # A work in progress! 3 | # 4 | # To build this docker image: 5 | # $> sudo docker build -t qq99/echoplexus-dev . 6 | # 7 | # To use this docker image: 8 | # $> sudo docker run -i -v /home/#{YOUR_USERNAME}/echoplexus:/echoplexus:rw -p #{YOUR_PREFERRED_PORTNUMBER}:8080 -t qq99/echoplexus-dev 9 | # From there, you can use tmux to spawn 2 windows and dev (`grunt` in one, `grunt exec` in another) 10 | # or do `grunt build; grunt exec:production` to run a near production mode of echoplexus 11 | FROM ubuntu 12 | RUN apt-get update 13 | RUN apt-get install -y build-essential python ruby git redis-server nodejs phantomjs npm 14 | RUN gem install sass 15 | RUN npm install -g coffee-script grunt grunt-cli supervisor bower testem browserify 16 | RUN ln -sf /usr/bin/nodejs /usr/bin/node 17 | RUN service redis-server start 18 | RUN apt-get install -y tmux 19 | EXPOSE 8080 20 | VOLUME ["/echoplexus"] 21 | ENTRYPOINT ["/usr/bin/tmux"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GPLv3 and MIT -------------------------------------------------------------------------------- /assets/echoplexus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/assets/echoplexus-logo.png -------------------------------------------------------------------------------- /assets/echoplexus-logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/assets/echoplexus-logo.psd -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echoplexus", 3 | "version": "0.2.0", 4 | "main": "server/main.js", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "components" 9 | ], 10 | "dependencies": { 11 | "codemirror": "~3.13.0", 12 | "requirejs": "~2.1.6", 13 | "jquery": "~2.0.2", 14 | "jquery.cookie": "~1.3.1", 15 | "moment": "~2.1.0", 16 | "keymaster": "latest", 17 | "backbone": "~1.1.2", 18 | "hammerjs": "~1.0.6", 19 | "backbone.stickit": "~0.7.0", 20 | "emojify.js": "https://github.com/qq99/emojify.js.git#92f56d20310b59bbc3ee905c1ad5fab7dbcf6e09", 21 | "fontawesome": "~4.0.3", 22 | "spectrum": "~1.3.4" 23 | }, 24 | "devDependencies": { 25 | "sinon": "~1.7.3", 26 | "chai": "~1.8.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/aes-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){for(var q=CryptoJS,x=q.lib.BlockCipher,r=q.algo,j=[],y=[],z=[],A=[],B=[],C=[],s=[],u=[],v=[],w=[],g=[],k=0;256>k;k++)g[k]=128>k?k<<1:k<<1^283;for(var n=0,l=0,k=0;256>k;k++){var f=l^l<<1^l<<2^l<<3^l<<4,f=f>>>8^f&255^99;j[n]=f;y[f]=n;var t=g[n],D=g[t],E=g[D],b=257*g[f]^16843008*f;z[n]=b<<24|b>>>8;A[n]=b<<16|b>>>16;B[n]=b<<8|b>>>24;C[n]=b;b=16843009*E^65537*D^257*t^16843008*n;s[f]=b<<24|b>>>8;u[f]=b<<16|b>>>16;v[f]=b<<8|b>>>24;w[f]=b;n?(n=t^g[g[g[E^t]]],l^=g[g[l]]):n=l=1}var F=[0,1,2,4,8, 8 | 16,32,64,128,27,54],r=r.AES=x.extend({_doReset:function(){for(var c=this._key,e=c.words,a=c.sigBytes/4,c=4*((this._nRounds=a+6)+1),b=this._keySchedule=[],h=0;h>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255]):(d=d<<8|d>>>24,d=j[d>>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255],d^=F[h/a|0]<<24);b[h]=b[h-a]^d}e=this._invKeySchedule=[];for(a=0;aa||4>=h?d:s[j[d>>>24]]^u[j[d>>>16&255]]^v[j[d>>> 9 | 8&255]]^w[j[d&255]]},encryptBlock:function(c,e){this._doCryptBlock(c,e,this._keySchedule,z,A,B,C,j)},decryptBlock:function(c,e){var a=c[e+1];c[e+1]=c[e+3];c[e+3]=a;this._doCryptBlock(c,e,this._invKeySchedule,s,u,v,w,y);a=c[e+1];c[e+1]=c[e+3];c[e+3]=a},_doCryptBlock:function(c,e,a,b,h,d,j,m){for(var n=this._nRounds,f=c[e]^a[0],g=c[e+1]^a[1],k=c[e+2]^a[2],p=c[e+3]^a[3],l=4,t=1;t>>24]^h[g>>>16&255]^d[k>>>8&255]^j[p&255]^a[l++],r=b[g>>>24]^h[k>>>16&255]^d[p>>>8&255]^j[f&255]^a[l++],s= 10 | b[k>>>24]^h[p>>>16&255]^d[f>>>8&255]^j[g&255]^a[l++],p=b[p>>>24]^h[f>>>16&255]^d[g>>>8&255]^j[k&255]^a[l++],f=q,g=r,k=s;q=(m[f>>>24]<<24|m[g>>>16&255]<<16|m[k>>>8&255]<<8|m[p&255])^a[l++];r=(m[g>>>24]<<24|m[k>>>16&255]<<16|m[p>>>8&255]<<8|m[f&255])^a[l++];s=(m[k>>>24]<<24|m[p>>>16&255]<<16|m[f>>>8&255]<<8|m[g&255])^a[l++];p=(m[p>>>24]<<24|m[f>>>16&255]<<16|m[g>>>8&255]<<8|m[k&255])^a[l++];c[e]=q;c[e+1]=r;c[e+2]=s;c[e+3]=p},keySize:8});q.AES=x._createHelper(r)})(); 11 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/core-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(h,r){var k={},l=k.lib={},n=function(){},f=l.Base={extend:function(a){n.prototype=this;var b=new n;a&&b.mixIn(a);b.hasOwnProperty("init")||(b.init=function(){b.$super.init.apply(this,arguments)});b.init.prototype=b;b.$super=this;return b},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | j=l.WordArray=f.extend({init:function(a,b){a=this.words=a||[];this.sigBytes=b!=r?b:4*a.length},toString:function(a){return(a||s).stringify(this)},concat:function(a){var b=this.words,d=a.words,c=this.sigBytes;a=a.sigBytes;this.clamp();if(c%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((c+e)%4);else if(65535>>2]=d[e>>>2];else b.push.apply(b,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<< 9 | 32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c, 10 | 2),16)<<24-4*(c%8);return new j.init(d,b/2)}},p=m.Latin1={stringify:function(a){var b=a.words;a=a.sigBytes;for(var d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return new j.init(d,b)}},t=m.Utf8={stringify:function(a){try{return decodeURIComponent(escape(p.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return p.parse(unescape(encodeURIComponent(a)))}}, 11 | q=l.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new j.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=t.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;c=h.min(4*a,c);if(a){for(var g=0;g>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d< 8 | e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 9 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/enc-base64.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function () { 8 | // Shortcuts 9 | var C = CryptoJS; 10 | var C_lib = C.lib; 11 | var WordArray = C_lib.WordArray; 12 | var C_enc = C.enc; 13 | 14 | /** 15 | * Base64 encoding strategy. 16 | */ 17 | var Base64 = C_enc.Base64 = { 18 | /** 19 | * Converts a word array to a Base64 string. 20 | * 21 | * @param {WordArray} wordArray The word array. 22 | * 23 | * @return {string} The Base64 string. 24 | * 25 | * @static 26 | * 27 | * @example 28 | * 29 | * var base64String = CryptoJS.enc.Base64.stringify(wordArray); 30 | */ 31 | stringify: function (wordArray) { 32 | // Shortcuts 33 | var words = wordArray.words; 34 | var sigBytes = wordArray.sigBytes; 35 | var map = this._map; 36 | 37 | // Clamp excess bits 38 | wordArray.clamp(); 39 | 40 | // Convert 41 | var base64Chars = []; 42 | for (var i = 0; i < sigBytes; i += 3) { 43 | var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 44 | var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; 45 | var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; 46 | 47 | var triplet = (byte1 << 16) | (byte2 << 8) | byte3; 48 | 49 | for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { 50 | base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); 51 | } 52 | } 53 | 54 | // Add padding 55 | var paddingChar = map.charAt(64); 56 | if (paddingChar) { 57 | while (base64Chars.length % 4) { 58 | base64Chars.push(paddingChar); 59 | } 60 | } 61 | 62 | return base64Chars.join(''); 63 | }, 64 | 65 | /** 66 | * Converts a Base64 string to a word array. 67 | * 68 | * @param {string} base64Str The Base64 string. 69 | * 70 | * @return {WordArray} The word array. 71 | * 72 | * @static 73 | * 74 | * @example 75 | * 76 | * var wordArray = CryptoJS.enc.Base64.parse(base64String); 77 | */ 78 | parse: function (base64Str) { 79 | // Shortcuts 80 | var base64StrLength = base64Str.length; 81 | var map = this._map; 82 | 83 | // Ignore padding 84 | var paddingChar = map.charAt(64); 85 | if (paddingChar) { 86 | var paddingIndex = base64Str.indexOf(paddingChar); 87 | if (paddingIndex != -1) { 88 | base64StrLength = paddingIndex; 89 | } 90 | } 91 | 92 | // Convert 93 | var words = []; 94 | var nBytes = 0; 95 | for (var i = 0; i < base64StrLength; i++) { 96 | if (i % 4) { 97 | var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); 98 | var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); 99 | words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); 100 | nBytes++; 101 | } 102 | } 103 | 104 | return WordArray.create(words, nBytes); 105 | }, 106 | 107 | _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 108 | }; 109 | }()); 110 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/enc-utf16-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var e=CryptoJS,f=e.lib.WordArray,e=e.enc;e.Utf16=e.Utf16BE={stringify:function(b){var d=b.words;b=b.sigBytes;for(var c=[],a=0;a>>2]>>>16-8*(a%4)&65535));return c.join("")},parse:function(b){for(var d=b.length,c=[],a=0;a>>1]|=b.charCodeAt(a)<<16-16*(a%2);return f.create(c,2*d)}};e.Utf16LE={stringify:function(b){var d=b.words;b=b.sigBytes;for(var c=[],a=0;a>>2]>>>16-8*(a%4)&65535)<<8&4278255360|(d[a>>> 8 | 2]>>>16-8*(a%4)&65535)>>>8&16711935));return c.join("")},parse:function(b){for(var d=b.length,c=[],a=0;a>>1,j=e[g],h=b.charCodeAt(a)<<16-16*(a%2);e[g]=j|h<<8&4278255360|h>>>8&16711935}return f.create(c,2*d)}}})(); 9 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/evpkdf-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var b=CryptoJS,a=b.lib,f=a.Base,k=a.WordArray,a=b.algo,l=a.EvpKDF=f.extend({cfg:f.extend({keySize:4,hasher:a.MD5,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,d=c.hasher.create(),g=k.create(),f=g.words,h=c.keySize,c=c.iterations;f.lengthe&&(b=a.finalize(b));b.clamp();for(var f=this._oKey=b.clone(),g=this._iKey=b.clone(),h=f.words,j=g.words,d=0;d>>2]|=a[c]<< 8 | 24-8*(c%4);e.call(this,d,b)}else e.apply(this,arguments)}).prototype=b}})(); 9 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/lib-typedarrays.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function () { 8 | // Check if typed arrays are supported 9 | if (typeof ArrayBuffer != 'function') { 10 | return; 11 | } 12 | 13 | // Shortcuts 14 | var C = CryptoJS; 15 | var C_lib = C.lib; 16 | var WordArray = C_lib.WordArray; 17 | 18 | // Reference original init 19 | var superInit = WordArray.init; 20 | 21 | // Augment WordArray.init to handle typed arrays 22 | var subInit = WordArray.init = function (typedArray) { 23 | // Convert buffers to uint8 24 | if (typedArray instanceof ArrayBuffer) { 25 | typedArray = new Uint8Array(typedArray); 26 | } 27 | 28 | // Convert other array views to uint8 29 | if ( 30 | typedArray instanceof Int8Array || 31 | typedArray instanceof Uint8ClampedArray || 32 | typedArray instanceof Int16Array || 33 | typedArray instanceof Uint16Array || 34 | typedArray instanceof Int32Array || 35 | typedArray instanceof Uint32Array || 36 | typedArray instanceof Float32Array || 37 | typedArray instanceof Float64Array 38 | ) { 39 | typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); 40 | } 41 | 42 | // Handle Uint8Array 43 | if (typedArray instanceof Uint8Array) { 44 | // Shortcut 45 | var typedArrayByteLength = typedArray.byteLength; 46 | 47 | // Extract bytes 48 | var words = []; 49 | for (var i = 0; i < typedArrayByteLength; i++) { 50 | words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); 51 | } 52 | 53 | // Initialize this word array 54 | superInit.call(this, words, typedArrayByteLength); 55 | } else { 56 | // Else call normal init 57 | superInit.apply(this, arguments); 58 | } 59 | }; 60 | 61 | subInit.prototype = WordArray; 62 | }()); 63 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/md5-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(E){function h(a,f,g,j,p,h,k){a=a+(f&g|~f&j)+p+k;return(a<>>32-h)+f}function k(a,f,g,j,p,h,k){a=a+(f&j|g&~j)+p+k;return(a<>>32-h)+f}function l(a,f,g,j,h,k,l){a=a+(f^g^j)+h+l;return(a<>>32-k)+f}function n(a,f,g,j,h,k,l){a=a+(g^(f|~j))+h+l;return(a<>>32-k)+f}for(var r=CryptoJS,q=r.lib,F=q.WordArray,s=q.Hasher,q=r.algo,a=[],t=0;64>t;t++)a[t]=4294967296*E.abs(E.sin(t+1))|0;q=q.MD5=s.extend({_doReset:function(){this._hash=new F.init([1732584193,4023233417,2562383102,271733878])}, 8 | _doProcessBlock:function(m,f){for(var g=0;16>g;g++){var j=f+g,p=m[j];m[j]=(p<<8|p>>>24)&16711935|(p<<24|p>>>8)&4278255360}var g=this._hash.words,j=m[f+0],p=m[f+1],q=m[f+2],r=m[f+3],s=m[f+4],t=m[f+5],u=m[f+6],v=m[f+7],w=m[f+8],x=m[f+9],y=m[f+10],z=m[f+11],A=m[f+12],B=m[f+13],C=m[f+14],D=m[f+15],b=g[0],c=g[1],d=g[2],e=g[3],b=h(b,c,d,e,j,7,a[0]),e=h(e,b,c,d,p,12,a[1]),d=h(d,e,b,c,q,17,a[2]),c=h(c,d,e,b,r,22,a[3]),b=h(b,c,d,e,s,7,a[4]),e=h(e,b,c,d,t,12,a[5]),d=h(d,e,b,c,u,17,a[6]),c=h(c,d,e,b,v,22,a[7]), 9 | b=h(b,c,d,e,w,7,a[8]),e=h(e,b,c,d,x,12,a[9]),d=h(d,e,b,c,y,17,a[10]),c=h(c,d,e,b,z,22,a[11]),b=h(b,c,d,e,A,7,a[12]),e=h(e,b,c,d,B,12,a[13]),d=h(d,e,b,c,C,17,a[14]),c=h(c,d,e,b,D,22,a[15]),b=k(b,c,d,e,p,5,a[16]),e=k(e,b,c,d,u,9,a[17]),d=k(d,e,b,c,z,14,a[18]),c=k(c,d,e,b,j,20,a[19]),b=k(b,c,d,e,t,5,a[20]),e=k(e,b,c,d,y,9,a[21]),d=k(d,e,b,c,D,14,a[22]),c=k(c,d,e,b,s,20,a[23]),b=k(b,c,d,e,x,5,a[24]),e=k(e,b,c,d,C,9,a[25]),d=k(d,e,b,c,r,14,a[26]),c=k(c,d,e,b,w,20,a[27]),b=k(b,c,d,e,B,5,a[28]),e=k(e,b, 10 | c,d,q,9,a[29]),d=k(d,e,b,c,v,14,a[30]),c=k(c,d,e,b,A,20,a[31]),b=l(b,c,d,e,t,4,a[32]),e=l(e,b,c,d,w,11,a[33]),d=l(d,e,b,c,z,16,a[34]),c=l(c,d,e,b,C,23,a[35]),b=l(b,c,d,e,p,4,a[36]),e=l(e,b,c,d,s,11,a[37]),d=l(d,e,b,c,v,16,a[38]),c=l(c,d,e,b,y,23,a[39]),b=l(b,c,d,e,B,4,a[40]),e=l(e,b,c,d,j,11,a[41]),d=l(d,e,b,c,r,16,a[42]),c=l(c,d,e,b,u,23,a[43]),b=l(b,c,d,e,x,4,a[44]),e=l(e,b,c,d,A,11,a[45]),d=l(d,e,b,c,D,16,a[46]),c=l(c,d,e,b,q,23,a[47]),b=n(b,c,d,e,j,6,a[48]),e=n(e,b,c,d,v,10,a[49]),d=n(d,e,b,c, 11 | C,15,a[50]),c=n(c,d,e,b,t,21,a[51]),b=n(b,c,d,e,A,6,a[52]),e=n(e,b,c,d,r,10,a[53]),d=n(d,e,b,c,y,15,a[54]),c=n(c,d,e,b,p,21,a[55]),b=n(b,c,d,e,w,6,a[56]),e=n(e,b,c,d,D,10,a[57]),d=n(d,e,b,c,u,15,a[58]),c=n(c,d,e,b,B,21,a[59]),b=n(b,c,d,e,s,6,a[60]),e=n(e,b,c,d,z,10,a[61]),d=n(d,e,b,c,q,15,a[62]),c=n(c,d,e,b,x,21,a[63]);g[0]=g[0]+b|0;g[1]=g[1]+c|0;g[2]=g[2]+d|0;g[3]=g[3]+e|0},_doFinalize:function(){var a=this._data,f=a.words,g=8*this._nDataBytes,j=8*a.sigBytes;f[j>>>5]|=128<<24-j%32;var h=E.floor(g/ 12 | 4294967296);f[(j+64>>>9<<4)+15]=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;f[(j+64>>>9<<4)+14]=(g<<8|g>>>24)&16711935|(g<<24|g>>>8)&4278255360;a.sigBytes=4*(f.length+1);this._process();a=this._hash;f=a.words;for(g=0;4>g;g++)j=f[g],f[g]=(j<<8|j>>>24)&16711935|(j<<24|j>>>8)&4278255360;return a},clone:function(){var a=s.clone.call(this);a._hash=this._hash.clone();return a}});r.MD5=s._createHelper(q);r.HmacMD5=s._createHmacHelper(q)})(Math); 13 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/mode-cfb-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.mode.CFB=function(){function g(c,b,e,a){var d=this._iv;d?(d=d.slice(0),this._iv=void 0):d=this._prevBlock;a.encryptBlock(d,0);for(a=0;a>24&255)){var c=a>>16&255,b=a>>8&255,e=a&255;255===c?(c=0,255===b?(b=0,255===e?e=0:++e):++b):++c;a=0+(c<<16)+(b<<8);a+=e}else a+=16777216;return a}var g=CryptoJS.lib.BlockCipherMode.extend(),j=g.Encryptor=g.extend({processBlock:function(a,c){var b=this._cipher,e=b.blockSize,d=this._iv,f=this._counter;d&&(f=this._counter=d.slice(0),this._iv=void 0);d=f;if(0===(d[0]=h(d[0])))d[1]=h(d[1]);f=f.slice(0);b.encryptBlock(f,0);for(b=0;b> 24) & 0xff) === 0xff) { //overflow 18 | var b1 = (word >> 16)&0xff; 19 | var b2 = (word >> 8)&0xff; 20 | var b3 = word & 0xff; 21 | 22 | if (b1 === 0xff) // overflow b1 23 | { 24 | b1 = 0; 25 | if (b2 === 0xff) 26 | { 27 | b2 = 0; 28 | if (b3 === 0xff) 29 | { 30 | b3 = 0; 31 | } 32 | else 33 | { 34 | ++b3; 35 | } 36 | } 37 | else 38 | { 39 | ++b2; 40 | } 41 | } 42 | else 43 | { 44 | ++b1; 45 | } 46 | 47 | word = 0; 48 | word += (b1 << 16); 49 | word += (b2 << 8); 50 | word += b3; 51 | } 52 | else 53 | { 54 | word += (0x01 << 24); 55 | } 56 | return word; 57 | } 58 | 59 | function incCounter(counter) 60 | { 61 | if ((counter[0] = incWord(counter[0])) === 0) 62 | { 63 | // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 64 | counter[1] = incWord(counter[1]); 65 | } 66 | return counter; 67 | } 68 | 69 | var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ 70 | processBlock: function (words, offset) { 71 | // Shortcuts 72 | var cipher = this._cipher 73 | var blockSize = cipher.blockSize; 74 | var iv = this._iv; 75 | var counter = this._counter; 76 | 77 | // Generate keystream 78 | if (iv) { 79 | counter = this._counter = iv.slice(0); 80 | 81 | // Remove IV for subsequent blocks 82 | this._iv = undefined; 83 | } 84 | 85 | incCounter(counter); 86 | 87 | var keystream = counter.slice(0); 88 | cipher.encryptBlock(keystream, 0); 89 | 90 | // Encrypt 91 | for (var i = 0; i < blockSize; i++) { 92 | words[offset + i] ^= keystream[i]; 93 | } 94 | } 95 | }); 96 | 97 | CTRGladman.Decryptor = Encryptor; 98 | 99 | return CTRGladman; 100 | }()); 101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/mode-ctr-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.mode.CTR=function(){var b=CryptoJS.lib.BlockCipherMode.extend(),g=b.Encryptor=b.extend({processBlock:function(b,f){var a=this._cipher,e=a.blockSize,c=this._iv,d=this._counter;c&&(d=this._counter=c.slice(0),this._iv=void 0);c=d.slice(0);a.encryptBlock(c,0);d[e-1]=d[e-1]+1|0;for(a=0;a>>2]|=c<<24-8*(b%4);a.sigBytes+=c},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}}; 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-ansix923.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /** 8 | * ANSI X.923 padding strategy. 9 | */ 10 | CryptoJS.pad.AnsiX923 = { 11 | pad: function (data, blockSize) { 12 | // Shortcuts 13 | var dataSigBytes = data.sigBytes; 14 | var blockSizeBytes = blockSize * 4; 15 | 16 | // Count padding bytes 17 | var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; 18 | 19 | // Compute last byte position 20 | var lastBytePos = dataSigBytes + nPaddingBytes - 1; 21 | 22 | // Pad 23 | data.clamp(); 24 | data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); 25 | data.sigBytes += nPaddingBytes; 26 | }, 27 | 28 | unpad: function (data) { 29 | // Get number of padding bytes from last byte 30 | var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; 31 | 32 | // Remove padding 33 | data.sigBytes -= nPaddingBytes; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-iso10126-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.pad.Iso10126={pad:function(a,c){var b=4*c,b=b-a.sigBytes%b;a.concat(CryptoJS.lib.WordArray.random(b-1)).concat(CryptoJS.lib.WordArray.create([b<<24],1))},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}}; 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-iso10126.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /** 8 | * ISO 10126 padding strategy. 9 | */ 10 | CryptoJS.pad.Iso10126 = { 11 | pad: function (data, blockSize) { 12 | // Shortcut 13 | var blockSizeBytes = blockSize * 4; 14 | 15 | // Count padding bytes 16 | var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; 17 | 18 | // Pad 19 | data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). 20 | concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); 21 | }, 22 | 23 | unpad: function (data) { 24 | // Get number of padding bytes from last byte 25 | var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; 26 | 27 | // Remove padding 28 | data.sigBytes -= nPaddingBytes; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-iso97971-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.pad.Iso97971={pad:function(a,b){a.concat(CryptoJS.lib.WordArray.create([2147483648],1));CryptoJS.pad.ZeroPadding.pad(a,b)},unpad:function(a){CryptoJS.pad.ZeroPadding.unpad(a);a.sigBytes--}}; 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-iso97971.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /** 8 | * ISO/IEC 9797-1 Padding Method 2. 9 | */ 10 | CryptoJS.pad.Iso97971 = { 11 | pad: function (data, blockSize) { 12 | // Add 0x80 byte 13 | data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); 14 | 15 | // Zero pad the rest 16 | CryptoJS.pad.ZeroPadding.pad(data, blockSize); 17 | }, 18 | 19 | unpad: function (data) { 20 | // Remove zero padding 21 | CryptoJS.pad.ZeroPadding.unpad(data); 22 | 23 | // Remove one more byte -- the 0x80 byte 24 | data.sigBytes--; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-nopadding-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.pad.NoPadding={pad:function(){},unpad:function(){}}; 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-nopadding.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /** 8 | * A noop padding strategy. 9 | */ 10 | CryptoJS.pad.NoPadding = { 11 | pad: function () { 12 | }, 13 | 14 | unpad: function () { 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-zeropadding-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | CryptoJS.pad.ZeroPadding={pad:function(a,c){var b=4*c;a.clamp();a.sigBytes+=b-(a.sigBytes%b||b)},unpad:function(a){for(var c=a.words,b=a.sigBytes-1;!(c[b>>>2]>>>24-8*(b%4)&255);)b--;a.sigBytes=b+1}}; 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pad-zeropadding.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /** 8 | * Zero padding strategy. 9 | */ 10 | CryptoJS.pad.ZeroPadding = { 11 | pad: function (data, blockSize) { 12 | // Shortcut 13 | var blockSizeBytes = blockSize * 4; 14 | 15 | // Pad 16 | data.clamp(); 17 | data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); 18 | }, 19 | 20 | unpad: function (data) { 21 | // Shortcut 22 | var dataWords = data.words; 23 | 24 | // Unpad 25 | var i = data.sigBytes - 1; 26 | while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { 27 | i--; 28 | } 29 | data.sigBytes = i + 1; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/pbkdf2-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var b=CryptoJS,a=b.lib,d=a.Base,m=a.WordArray,a=b.algo,q=a.HMAC,l=a.PBKDF2=d.extend({cfg:d.extend({keySize:4,hasher:a.SHA1,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,f=q.create(c.hasher,a),g=m.create(),d=m.create([1]),l=g.words,r=d.words,n=c.keySize,c=c.iterations;l.lengthc;c++)f[c]=d[c];d[0]=d[0]+1295307597+this._b|0;d[1]=d[1]+3545052371+(d[0]>>>0>>0?1:0)|0;d[2]=d[2]+886263092+(d[1]>>>0>>0?1:0)|0;d[3]=d[3]+1295307597+(d[2]>>>0>>0?1:0)|0;d[4]=d[4]+3545052371+(d[3]>>>0>>0?1:0)|0;d[5]=d[5]+886263092+(d[4]>>>0>>0?1:0)|0;d[6]=d[6]+1295307597+(d[5]>>>0>>0?1:0)|0;d[7]=d[7]+3545052371+(d[6]>>>0>>0?1:0)|0;this._b=d[7]>>>0>>0?1:0;for(c=0;8>c;c++){var h=a[c]+d[c],e=h&65535, 8 | g=h>>>16;b[c]=((e*e>>>17)+e*g>>>15)+g*g^((h&4294901760)*h|0)+((h&65535)*h|0)}a[0]=b[0]+(b[7]<<16|b[7]>>>16)+(b[6]<<16|b[6]>>>16)|0;a[1]=b[1]+(b[0]<<8|b[0]>>>24)+b[7]|0;a[2]=b[2]+(b[1]<<16|b[1]>>>16)+(b[0]<<16|b[0]>>>16)|0;a[3]=b[3]+(b[2]<<8|b[2]>>>24)+b[1]|0;a[4]=b[4]+(b[3]<<16|b[3]>>>16)+(b[2]<<16|b[2]>>>16)|0;a[5]=b[5]+(b[4]<<8|b[4]>>>24)+b[3]|0;a[6]=b[6]+(b[5]<<16|b[5]>>>16)+(b[4]<<16|b[4]>>>16)|0;a[7]=b[7]+(b[6]<<8|b[6]>>>24)+b[5]|0}var j=CryptoJS,k=j.lib.StreamCipher,e=[],f=[],b=[],l=j.algo.RabbitLegacy= 9 | k.extend({_doReset:function(){for(var a=this._key.words,d=this.cfg.iv,c=this._X=[a[0],a[3]<<16|a[2]>>>16,a[1],a[0]<<16|a[3]>>>16,a[2],a[1]<<16|a[0]>>>16,a[3],a[2]<<16|a[1]>>>16],a=this._C=[a[2]<<16|a[2]>>>16,a[0]&4294901760|a[1]&65535,a[3]<<16|a[3]>>>16,a[1]&4294901760|a[2]&65535,a[0]<<16|a[0]>>>16,a[2]&4294901760|a[3]&65535,a[1]<<16|a[1]>>>16,a[3]&4294901760|a[0]&65535],b=this._b=0;4>b;b++)g.call(this);for(b=0;8>b;b++)a[b]^=c[b+4&7];if(d){var c=d.words,d=c[0],c=c[1],d=(d<<8|d>>>24)&16711935|(d<< 10 | 24|d>>>8)&4278255360,c=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360,b=d>>>16|c&4294901760,e=c<<16|d&65535;a[0]^=d;a[1]^=b;a[2]^=c;a[3]^=e;a[4]^=d;a[5]^=b;a[6]^=c;a[7]^=e;for(b=0;4>b;b++)g.call(this)}},_doProcessBlock:function(a,b){var c=this._X;g.call(this);e[0]=c[0]^c[5]>>>16^c[3]<<16;e[1]=c[2]^c[7]>>>16^c[5]<<16;e[2]=c[4]^c[1]>>>16^c[7]<<16;e[3]=c[6]^c[3]>>>16^c[1]<<16;for(c=0;4>c;c++)e[c]=(e[c]<<8|e[c]>>>24)&16711935|(e[c]<<24|e[c]>>>8)&4278255360,a[b+c]^=e[c]},blockSize:4,ivSize:2});j.RabbitLegacy= 11 | k._createHelper(l)})(); 12 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/rabbit-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){function g(){for(var b=this._X,d=this._C,a=0;8>a;a++)f[a]=d[a];d[0]=d[0]+1295307597+this._b|0;d[1]=d[1]+3545052371+(d[0]>>>0>>0?1:0)|0;d[2]=d[2]+886263092+(d[1]>>>0>>0?1:0)|0;d[3]=d[3]+1295307597+(d[2]>>>0>>0?1:0)|0;d[4]=d[4]+3545052371+(d[3]>>>0>>0?1:0)|0;d[5]=d[5]+886263092+(d[4]>>>0>>0?1:0)|0;d[6]=d[6]+1295307597+(d[5]>>>0>>0?1:0)|0;d[7]=d[7]+3545052371+(d[6]>>>0>>0?1:0)|0;this._b=d[7]>>>0>>0?1:0;for(a=0;8>a;a++){var h=b[a]+d[a],e=h&65535, 8 | g=h>>>16;c[a]=((e*e>>>17)+e*g>>>15)+g*g^((h&4294901760)*h|0)+((h&65535)*h|0)}b[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0;b[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0;b[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0;b[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0;b[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0;b[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0;b[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0;b[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var j=CryptoJS,k=j.lib.StreamCipher,e=[],f=[],c=[],l=j.algo.Rabbit= 9 | k.extend({_doReset:function(){for(var b=this._key.words,d=this.cfg.iv,a=0;4>a;a++)b[a]=(b[a]<<8|b[a]>>>24)&16711935|(b[a]<<24|b[a]>>>8)&4278255360;for(var c=this._X=[b[0],b[3]<<16|b[2]>>>16,b[1],b[0]<<16|b[3]>>>16,b[2],b[1]<<16|b[0]>>>16,b[3],b[2]<<16|b[1]>>>16],b=this._C=[b[2]<<16|b[2]>>>16,b[0]&4294901760|b[1]&65535,b[3]<<16|b[3]>>>16,b[1]&4294901760|b[2]&65535,b[0]<<16|b[0]>>>16,b[2]&4294901760|b[3]&65535,b[1]<<16|b[1]>>>16,b[3]&4294901760|b[0]&65535],a=this._b=0;4>a;a++)g.call(this);for(a=0;8> 10 | a;a++)b[a]^=c[a+4&7];if(d){var a=d.words,d=a[0],a=a[1],d=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360,a=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360,c=d>>>16|a&4294901760,e=a<<16|d&65535;b[0]^=d;b[1]^=c;b[2]^=a;b[3]^=e;b[4]^=d;b[5]^=c;b[6]^=a;b[7]^=e;for(a=0;4>a;a++)g.call(this)}},_doProcessBlock:function(b,c){var a=this._X;g.call(this);e[0]=a[0]^a[5]>>>16^a[3]<<16;e[1]=a[2]^a[7]>>>16^a[5]<<16;e[2]=a[4]^a[1]>>>16^a[7]<<16;e[3]=a[6]^a[3]>>>16^a[1]<<16;for(a=0;4>a;a++)e[a]=(e[a]<<8|e[a]>>>24)& 11 | 16711935|(e[a]<<24|e[a]>>>8)&4278255360,b[c+a]^=e[a]},blockSize:4,ivSize:2});j.Rabbit=k._createHelper(l)})(); 12 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/rc4-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){function l(){for(var a=this._S,d=this._i,c=this._j,b=0,e=0;4>e;e++){var d=(d+1)%256,c=(c+a[d])%256,f=a[d];a[d]=a[c];a[c]=f;b|=a[(a[d]+a[c])%256]<<24-8*e}this._i=d;this._j=c;return b}var g=CryptoJS,k=g.lib.StreamCipher,h=g.algo,j=h.RC4=k.extend({_doReset:function(){for(var a=this._key,d=a.words,a=a.sigBytes,c=this._S=[],b=0;256>b;b++)c[b]=b;for(var e=b=0;256>b;b++){var f=b%a,e=(e+c[b]+(d[f>>>2]>>>24-8*(f%4)&255))%256,f=c[b];c[b]=c[e];c[e]=f}this._i=this._j=0},_doProcessBlock:function(a, 8 | d){a[d]^=l.call(this)},keySize:8,ivSize:0});g.RC4=k._createHelper(j);h=h.RC4Drop=j.extend({cfg:j.cfg.extend({drop:192}),_doReset:function(){j._doReset.call(this);for(var a=this.cfg.drop;0>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; 34 | 35 | j = (j + S[i] + keyByte) % 256; 36 | 37 | // Swap 38 | var t = S[i]; 39 | S[i] = S[j]; 40 | S[j] = t; 41 | } 42 | 43 | // Counters 44 | this._i = this._j = 0; 45 | }, 46 | 47 | _doProcessBlock: function (M, offset) { 48 | M[offset] ^= generateKeystreamWord.call(this); 49 | }, 50 | 51 | keySize: 256/32, 52 | 53 | ivSize: 0 54 | }); 55 | 56 | function generateKeystreamWord() { 57 | // Shortcuts 58 | var S = this._S; 59 | var i = this._i; 60 | var j = this._j; 61 | 62 | // Generate keystream word 63 | var keystreamWord = 0; 64 | for (var n = 0; n < 4; n++) { 65 | i = (i + 1) % 256; 66 | j = (j + S[i]) % 256; 67 | 68 | // Swap 69 | var t = S[i]; 70 | S[i] = S[j]; 71 | S[j] = t; 72 | 73 | keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); 74 | } 75 | 76 | // Update counters 77 | this._i = i; 78 | this._j = j; 79 | 80 | return keystreamWord; 81 | } 82 | 83 | /** 84 | * Shortcut functions to the cipher's object interface. 85 | * 86 | * @example 87 | * 88 | * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); 89 | * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); 90 | */ 91 | C.RC4 = StreamCipher._createHelper(RC4); 92 | 93 | /** 94 | * Modified RC4 stream cipher algorithm. 95 | */ 96 | var RC4Drop = C_algo.RC4Drop = RC4.extend({ 97 | /** 98 | * Configuration options. 99 | * 100 | * @property {number} drop The number of keystream words to drop. Default 192 101 | */ 102 | cfg: RC4.cfg.extend({ 103 | drop: 192 104 | }), 105 | 106 | _doReset: function () { 107 | RC4._doReset.call(this); 108 | 109 | // Drop 110 | for (var i = this.cfg.drop; i > 0; i--) { 111 | generateKeystreamWord.call(this); 112 | } 113 | } 114 | }); 115 | 116 | /** 117 | * Shortcut functions to the cipher's object interface. 118 | * 119 | * @example 120 | * 121 | * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); 122 | * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); 123 | */ 124 | C.RC4Drop = StreamCipher._createHelper(RC4Drop); 125 | }()); 126 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/ripemd160-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | /* 8 | 9 | (c) 2012 by C?dric Mesnil. All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 12 | 13 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 14 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 | */ 18 | (function(){var q=CryptoJS,d=q.lib,n=d.WordArray,p=d.Hasher,d=q.algo,x=n.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),y=n.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),z=n.create([11,14,15,12, 19 | 5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),A=n.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),B=n.create([0,1518500249,1859775393,2400959708,2840853838]),C=n.create([1352829926,1548603684,1836072691, 20 | 2053994217,0]),d=d.RIPEMD160=p.extend({_doReset:function(){this._hash=n.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(e,v){for(var b=0;16>b;b++){var c=v+b,f=e[c];e[c]=(f<<8|f>>>24)&16711935|(f<<24|f>>>8)&4278255360}var c=this._hash.words,f=B.words,d=C.words,n=x.words,q=y.words,p=z.words,w=A.words,t,g,h,j,r,u,k,l,m,s;u=t=c[0];k=g=c[1];l=h=c[2];m=j=c[3];s=r=c[4];for(var a,b=0;80>b;b+=1)a=t+e[v+n[b]]|0,a=16>b?a+((g^h^j)+f[0]):32>b?a+((g&h|~g&j)+f[1]):48>b? 21 | a+(((g|~h)^j)+f[2]):64>b?a+((g&j|h&~j)+f[3]):a+((g^(h|~j))+f[4]),a|=0,a=a<>>32-p[b],a=a+r|0,t=r,r=j,j=h<<10|h>>>22,h=g,g=a,a=u+e[v+q[b]]|0,a=16>b?a+((k^(l|~m))+d[0]):32>b?a+((k&m|l&~m)+d[1]):48>b?a+(((k|~l)^m)+d[2]):64>b?a+((k&l|~k&m)+d[3]):a+((k^l^m)+d[4]),a|=0,a=a<>>32-w[b],a=a+s|0,u=s,s=m,m=l<<10|l>>>22,l=k,k=a;a=c[1]+h+m|0;c[1]=c[2]+j+s|0;c[2]=c[3]+r+u|0;c[3]=c[4]+t+k|0;c[4]=c[0]+g+l|0;c[0]=a},_doFinalize:function(){var e=this._data,d=e.words,b=8*this._nDataBytes,c=8*e.sigBytes; 22 | d[c>>>5]|=128<<24-c%32;d[(c+64>>>9<<4)+14]=(b<<8|b>>>24)&16711935|(b<<24|b>>>8)&4278255360;e.sigBytes=4*(d.length+1);this._process();e=this._hash;d=e.words;for(b=0;5>b;b++)c=d[b],d[b]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return e},clone:function(){var d=p.clone.call(this);d._hash=this._hash.clone();return d}});q.RIPEMD160=p._createHelper(d);q.HmacRIPEMD160=p._createHmacHelper(d)})(Math); 23 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha1-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var k=CryptoJS,b=k.lib,m=b.WordArray,l=b.Hasher,d=[],b=k.algo.SHA1=l.extend({_doReset:function(){this._hash=new m.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(n,p){for(var a=this._hash.words,e=a[0],f=a[1],h=a[2],j=a[3],b=a[4],c=0;80>c;c++){if(16>c)d[c]=n[p+c]|0;else{var g=d[c-3]^d[c-8]^d[c-14]^d[c-16];d[c]=g<<1|g>>>31}g=(e<<5|e>>>27)+b+d[c];g=20>c?g+((f&h|~f&j)+1518500249):40>c?g+((f^h^j)+1859775393):60>c?g+((f&h|f&j|h&j)-1894007588):g+((f^h^ 8 | j)-899497514);b=j;j=h;h=f<<30|f>>>2;f=e;e=g}a[0]=a[0]+e|0;a[1]=a[1]+f|0;a[2]=a[2]+h|0;a[3]=a[3]+j|0;a[4]=a[4]+b|0},_doFinalize:function(){var b=this._data,d=b.words,a=8*this._nDataBytes,e=8*b.sigBytes;d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=Math.floor(a/4294967296);d[(e+64>>>9<<4)+15]=a;b.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var b=l.clone.call(this);b._hash=this._hash.clone();return b}});k.SHA1=l._createHelper(b);k.HmacSHA1=l._createHmacHelper(b)})(); 9 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha224-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var b=CryptoJS,d=b.lib.WordArray,a=b.algo,c=a.SHA256,a=a.SHA224=c.extend({_doReset:function(){this._hash=new d.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var a=c._doFinalize.call(this);a.sigBytes-=4;return a}});b.SHA224=c._createHelper(a);b.HmacSHA224=c._createHmacHelper(a)})(); 8 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha224.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function () { 8 | // Shortcuts 9 | var C = CryptoJS; 10 | var C_lib = C.lib; 11 | var WordArray = C_lib.WordArray; 12 | var C_algo = C.algo; 13 | var SHA256 = C_algo.SHA256; 14 | 15 | /** 16 | * SHA-224 hash algorithm. 17 | */ 18 | var SHA224 = C_algo.SHA224 = SHA256.extend({ 19 | _doReset: function () { 20 | this._hash = new WordArray.init([ 21 | 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 22 | 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 23 | ]); 24 | }, 25 | 26 | _doFinalize: function () { 27 | var hash = SHA256._doFinalize.call(this); 28 | 29 | hash.sigBytes -= 4; 30 | 31 | return hash; 32 | } 33 | }); 34 | 35 | /** 36 | * Shortcut function to the hasher's object interface. 37 | * 38 | * @param {WordArray|string} message The message to hash. 39 | * 40 | * @return {WordArray} The hash. 41 | * 42 | * @static 43 | * 44 | * @example 45 | * 46 | * var hash = CryptoJS.SHA224('message'); 47 | * var hash = CryptoJS.SHA224(wordArray); 48 | */ 49 | C.SHA224 = SHA256._createHelper(SHA224); 50 | 51 | /** 52 | * Shortcut function to the HMAC's object interface. 53 | * 54 | * @param {WordArray|string} message The message to hash. 55 | * @param {WordArray|string} key The secret key. 56 | * 57 | * @return {WordArray} The HMAC. 58 | * 59 | * @static 60 | * 61 | * @example 62 | * 63 | * var hmac = CryptoJS.HmacSHA224(message, key); 64 | */ 65 | C.HmacSHA224 = SHA256._createHmacHelper(SHA224); 66 | }()); 67 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha256-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(k){for(var g=CryptoJS,h=g.lib,v=h.WordArray,j=h.Hasher,h=g.algo,s=[],t=[],u=function(q){return 4294967296*(q-(q|0))|0},l=2,b=0;64>b;){var d;a:{d=l;for(var w=k.sqrt(d),r=2;r<=w;r++)if(!(d%r)){d=!1;break a}d=!0}d&&(8>b&&(s[b]=u(k.pow(l,0.5))),t[b]=u(k.pow(l,1/3)),b++);l++}var n=[],h=h.SHA256=j.extend({_doReset:function(){this._hash=new v.init(s.slice(0))},_doProcessBlock:function(q,h){for(var a=this._hash.words,c=a[0],d=a[1],b=a[2],k=a[3],f=a[4],g=a[5],j=a[6],l=a[7],e=0;64>e;e++){if(16>e)n[e]= 8 | q[h+e]|0;else{var m=n[e-15],p=n[e-2];n[e]=((m<<25|m>>>7)^(m<<14|m>>>18)^m>>>3)+n[e-7]+((p<<15|p>>>17)^(p<<13|p>>>19)^p>>>10)+n[e-16]}m=l+((f<<26|f>>>6)^(f<<21|f>>>11)^(f<<7|f>>>25))+(f&g^~f&j)+t[e]+n[e];p=((c<<30|c>>>2)^(c<<19|c>>>13)^(c<<10|c>>>22))+(c&d^c&b^d&b);l=j;j=g;g=f;f=k+m|0;k=b;b=d;d=c;c=m+p|0}a[0]=a[0]+c|0;a[1]=a[1]+d|0;a[2]=a[2]+b|0;a[3]=a[3]+k|0;a[4]=a[4]+f|0;a[5]=a[5]+g|0;a[6]=a[6]+j|0;a[7]=a[7]+l|0},_doFinalize:function(){var d=this._data,b=d.words,a=8*this._nDataBytes,c=8*d.sigBytes; 9 | b[c>>>5]|=128<<24-c%32;b[(c+64>>>9<<4)+14]=k.floor(a/4294967296);b[(c+64>>>9<<4)+15]=a;d.sigBytes=4*b.length;this._process();return this._hash},clone:function(){var b=j.clone.call(this);b._hash=this._hash.clone();return b}});g.SHA256=j._createHelper(h);g.HmacSHA256=j._createHmacHelper(h)})(Math); 10 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha3-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(y){for(var p=CryptoJS,m=p.lib,z=m.WordArray,q=m.Hasher,s=p.x64.Word,m=p.algo,v=[],w=[],x=[],c=1,d=0,l=0;24>l;l++){v[c+5*d]=(l+1)*(l+2)/2%64;var r=(2*c+3*d)%5,c=d%5,d=r}for(c=0;5>c;c++)for(d=0;5>d;d++)w[c+5*d]=d+5*((2*c+3*d)%5);c=1;for(d=0;24>d;d++){for(var t=r=l=0;7>t;t++){if(c&1){var u=(1<u?r^=1<c;c++)j[c]=s.create();m=m.SHA3=q.extend({cfg:q.cfg.extend({outputLength:512}),_doReset:function(){for(var c=this._state= 8 | [],n=0;25>n;n++)c[n]=new s.init;this.blockSize=(1600-2*this.cfg.outputLength)/32},_doProcessBlock:function(c,n){for(var h=this._state,d=this.blockSize/2,b=0;b>>24)&16711935|(e<<24|e>>>8)&4278255360,f=(f<<8|f>>>24)&16711935|(f<<24|f>>>8)&4278255360,a=h[b];a.high^=f;a.low^=e}for(d=0;24>d;d++){for(b=0;5>b;b++){for(var k=e=0,g=0;5>g;g++)a=h[b+5*g],e^=a.high,k^=a.low;a=j[b];a.high=e;a.low=k}for(b=0;5>b;b++){a=j[(b+4)%5];e=j[(b+1)%5];f=e.high;g=e.low;e=a.high^ 9 | (f<<1|g>>>31);k=a.low^(g<<1|f>>>31);for(g=0;5>g;g++)a=h[b+5*g],a.high^=e,a.low^=k}for(f=1;25>f;f++)a=h[f],b=a.high,a=a.low,g=v[f],32>g?(e=b<>>32-g,k=a<>>32-g):(e=a<>>64-g,k=b<>>64-g),a=j[w[f]],a.high=e,a.low=k;a=j[0];b=h[0];a.high=b.high;a.low=b.low;for(b=0;5>b;b++)for(g=0;5>g;g++)f=b+5*g,a=h[f],e=j[f],f=j[(b+1)%5+5*g],k=j[(b+2)%5+5*g],a.high=e.high^~f.high&k.high,a.low=e.low^~f.low&k.low;a=h[0];b=x[d];a.high^=b.high;a.low^=b.low}},_doFinalize:function(){var c=this._data, 10 | d=c.words,h=8*c.sigBytes,j=32*this.blockSize;d[h>>>5]|=1<<24-h%32;d[(y.ceil((h+1)/j)*j>>>5)-1]|=128;c.sigBytes=4*d.length;this._process();for(var c=this._state,d=this.cfg.outputLength/8,h=d/8,j=[],b=0;b>>24)&16711935|(f<<24|f>>>8)&4278255360,e=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;j.push(e);j.push(f)}return new z.init(j,d)},clone:function(){for(var c=q.clone.call(this),d=c._state=this._state.slice(0),h=0;25>h;h++)d[h]=d[h].clone();return c}}); 11 | p.SHA3=q._createHelper(m);p.HmacSHA3=q._createHmacHelper(m)})(Math); 12 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha384-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var c=CryptoJS,a=c.x64,b=a.Word,e=a.WordArray,a=c.algo,d=a.SHA512,a=a.SHA384=d.extend({_doReset:function(){this._hash=new e.init([new b.init(3418070365,3238371032),new b.init(1654270250,914150663),new b.init(2438529370,812702999),new b.init(355462360,4144912697),new b.init(1731405415,4290775857),new b.init(2394180231,1750603025),new b.init(3675008525,1694076839),new b.init(1203062813,3204075428)])},_doFinalize:function(){var a=d._doFinalize.call(this);a.sigBytes-=16;return a}});c.SHA384= 8 | d._createHelper(a);c.HmacSHA384=d._createHmacHelper(a)})(); 9 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/sha384.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function () { 8 | // Shortcuts 9 | var C = CryptoJS; 10 | var C_x64 = C.x64; 11 | var X64Word = C_x64.Word; 12 | var X64WordArray = C_x64.WordArray; 13 | var C_algo = C.algo; 14 | var SHA512 = C_algo.SHA512; 15 | 16 | /** 17 | * SHA-384 hash algorithm. 18 | */ 19 | var SHA384 = C_algo.SHA384 = SHA512.extend({ 20 | _doReset: function () { 21 | this._hash = new X64WordArray.init([ 22 | new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), 23 | new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), 24 | new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), 25 | new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) 26 | ]); 27 | }, 28 | 29 | _doFinalize: function () { 30 | var hash = SHA512._doFinalize.call(this); 31 | 32 | hash.sigBytes -= 16; 33 | 34 | return hash; 35 | } 36 | }); 37 | 38 | /** 39 | * Shortcut function to the hasher's object interface. 40 | * 41 | * @param {WordArray|string} message The message to hash. 42 | * 43 | * @return {WordArray} The hash. 44 | * 45 | * @static 46 | * 47 | * @example 48 | * 49 | * var hash = CryptoJS.SHA384('message'); 50 | * var hash = CryptoJS.SHA384(wordArray); 51 | */ 52 | C.SHA384 = SHA512._createHelper(SHA384); 53 | 54 | /** 55 | * Shortcut function to the HMAC's object interface. 56 | * 57 | * @param {WordArray|string} message The message to hash. 58 | * @param {WordArray|string} key The secret key. 59 | * 60 | * @return {WordArray} The HMAC. 61 | * 62 | * @static 63 | * 64 | * @example 65 | * 66 | * var hmac = CryptoJS.HmacSHA384(message, key); 67 | */ 68 | C.HmacSHA384 = SHA512._createHmacHelper(SHA384); 69 | }()); 70 | -------------------------------------------------------------------------------- /lib/CryptoJS-3.1.2/components/x64-core-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(g){var a=CryptoJS,f=a.lib,e=f.Base,h=f.WordArray,a=a.x64={};a.Word=e.extend({init:function(b,c){this.high=b;this.low=c}});a.WordArray=e.extend({init:function(b,c){b=this.words=b||[];this.sigBytes=c!=g?c:8*b.length},toX32:function(){for(var b=this.words,c=b.length,a=[],d=0;d 2 | 3 | CodeFrame 4 | 5 | 57 | 58 | 59 |
60 | Protips: 61 |
    62 |
  • All code is executed in a sandboxed iframe. That said, it's wise not to run sketchy code.
  • 63 |
  • Certain annoying functions like alert() have been aliased away to increase the effort required to annoy someone
  • 64 |
  • Use LiveReload to evaluate while you type. This could be a security risk or denial of service (while (true){}) if you don't trust your chat particpants.
  • 65 |
  • 66 | If you want to see the result of an expression, the last line of your code should return it! (e.g., return 5*5;) 67 |
  • 68 |
  • console.log(...) will not work here :( Use return
  • 69 |
  • jQuery, Backbone, underscore are available
  • 70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /public/echoplexus-logo-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-120.png -------------------------------------------------------------------------------- /public/echoplexus-logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-128.png -------------------------------------------------------------------------------- /public/echoplexus-logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-16.png -------------------------------------------------------------------------------- /public/echoplexus-logo-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-30.png -------------------------------------------------------------------------------- /public/echoplexus-logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-32.png -------------------------------------------------------------------------------- /public/echoplexus-logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-48.png -------------------------------------------------------------------------------- /public/echoplexus-logo-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-60.png -------------------------------------------------------------------------------- /public/echoplexus-logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-64.png -------------------------------------------------------------------------------- /public/echoplexus-logo-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo-90.png -------------------------------------------------------------------------------- /public/echoplexus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/echoplexus-logo.png -------------------------------------------------------------------------------- /public/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | echoplexus 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/embedded-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | embedded echoplexus examples 5 | 6 | 7 | 8 | 18 | 19 | 20 |
21 |

Blog article number one

22 |

Hello world

23 |

Lorem ipsum dolor sit amet, et qui autem imperdiet, est quis cetero in, graeci vocibus minimum ei sea. Wisi diceret lucilius vix eu, id natum nominavi per. Vis at sensibus prodesset, eligendi scribentur ut nec. Ludus nihil semper per te, ne volutpat erroribus duo. Omnis legimus has no, sit quas assueverit eu, eu possim vivendum per. In percipit expetenda appellantur est, mea atqui nostrum cu, cetero vocent pri an.

24 |

Inermis dignissim mel ad. Mei ei commodo deseruisse. Ut graece evertitur repudiandae eam, id lorem ridens discere ius, et causae delenit docendi sit. Ea vix ullum tritani detraxit. Mel magna mazim dicunt in, eam feugait disputando in, an per legere suscipiantur. Ut nisl scripta mediocrem mel. Partem ornatus eligendi vis an, per cu modo vero voluptatum.

25 |

Comments on this article:

26 | 27 |
28 |
29 |
30 |

Blog article number two

31 |

Hiya world

32 |

Lorem ipsum dolor sit amet, et qui autem imperdiet, est quis cetero in, graeci vocibus minimum ei sea. Wisi diceret lucilius vix eu, id natum nominavi per. Vis at sensibus prodesset, eligendi scribentur ut nec. Ludus nihil semper per te, ne volutpat erroribus duo. Omnis legimus has no, sit quas assueverit eu, eu possim vivendum per. In percipit expetenda appellantur est, mea atqui nostrum cu, cetero vocent pri an.

33 |

Inermis dignissim mel ad. Mei ei commodo deseruisse. Ut graece evertitur repudiandae eam, id lorem ridens discere ius, et causae delenit docendi sit. Ea vix ullum tritani detraxit. Mel magna mazim dicunt in, eam feugait disputando in, an per legere suscipiantur. Ut nisl scripta mediocrem mel. Partem ornatus eligendi vis an, per cu modo vero voluptatum.

34 |

Comments on this article:

35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /public/favicon-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/favicon-activity.png -------------------------------------------------------------------------------- /public/favicon-disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/favicon-disconnected.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/favicon.png -------------------------------------------------------------------------------- /public/install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.0.4", 3 | "name" : "echoplexus", 4 | "launch_path" : "/", 5 | 6 | "installs_allowed_from" : ["*"], 7 | 8 | "developer": { 9 | "name": "Anthony Cameron", 10 | "url": "https://echoplex.us" 11 | }, 12 | 13 | "default_locale": "en", 14 | 15 | "description": "Anonymous chat (alpha version)", 16 | 17 | "icons": { 18 | "16" : "/echoplexus-logo-16.png", 19 | "32" : "/echoplexus-logo-32.png", 20 | "48" : "/echoplexus-logo-48.png", 21 | "60" : "/echoplexus-logo-60.png", 22 | "64" : "/echoplexus-logo-64.png", 23 | "90" : "/echoplexus-logo-90.png", 24 | "120" : "/echoplexus-logo-120.png", 25 | "128" : "/echoplexus-logo-128.png" 26 | }, 27 | 28 | "default_locale" : "en", 29 | 30 | "permissions" : { 31 | "desktop-notification": { 32 | "description" : "To show notifications" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/noisy_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/noisy_grid.png -------------------------------------------------------------------------------- /public/noisy_net.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/public/noisy_net.png -------------------------------------------------------------------------------- /sass/_animation.scss: -------------------------------------------------------------------------------- 1 | // http://www.w3.org/TR/css3-animations/#the-animation-name-property- 2 | // Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. 3 | 4 | // Official animation shorthand property. 5 | @mixin animation ($animations...) { 6 | @include prefixer(animation, $animations, webkit moz spec); 7 | } 8 | 9 | // Individual Animation Properties 10 | @mixin animation-name ($names...) { 11 | @include prefixer(animation-name, $names, webkit moz spec); 12 | } 13 | 14 | 15 | @mixin animation-duration ($times...) { 16 | @include prefixer(animation-duration, $times, webkit moz spec); 17 | } 18 | 19 | 20 | @mixin animation-timing-function ($motions...) { 21 | // ease | linear | ease-in | ease-out | ease-in-out 22 | @include prefixer(animation-timing-function, $motions, webkit moz spec); 23 | } 24 | 25 | 26 | @mixin animation-iteration-count ($values...) { 27 | // infinite | 28 | @include prefixer(animation-iteration-count, $values, webkit moz spec); 29 | } 30 | 31 | 32 | @mixin animation-direction ($directions...) { 33 | // normal | alternate 34 | @include prefixer(animation-direction, $directions, webkit moz spec); 35 | } 36 | 37 | 38 | @mixin animation-play-state ($states...) { 39 | // running | paused 40 | @include prefixer(animation-play-state, $states, webkit moz spec); 41 | } 42 | 43 | 44 | @mixin animation-delay ($times...) { 45 | @include prefixer(animation-delay, $times, webkit moz spec); 46 | } 47 | 48 | 49 | @mixin animation-fill-mode ($modes...) { 50 | // none | forwards | backwards | both 51 | @include prefixer(animation-fill-mode, $modes, webkit moz spec); 52 | } 53 | -------------------------------------------------------------------------------- /sass/_keyframes.scss: -------------------------------------------------------------------------------- 1 | // Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content 2 | @mixin keyframes($name) { 3 | $original-prefix-for-webkit: $prefix-for-webkit; 4 | $original-prefix-for-mozilla: $prefix-for-mozilla; 5 | $original-prefix-for-microsoft: $prefix-for-microsoft; 6 | $original-prefix-for-opera: $prefix-for-opera; 7 | $original-prefix-for-spec: $prefix-for-spec; 8 | 9 | @if $original-prefix-for-webkit { 10 | @include disable-prefix-for-all(); 11 | $prefix-for-webkit: true; 12 | @-webkit-keyframes #{$name} { 13 | @content; 14 | } 15 | } 16 | @if $original-prefix-for-mozilla { 17 | @include disable-prefix-for-all(); 18 | $prefix-for-mozilla: true; 19 | @-moz-keyframes #{$name} { 20 | @content; 21 | } 22 | } 23 | @if $original-prefix-for-opera { 24 | @include disable-prefix-for-all(); 25 | $prefix-for-opera: true; 26 | @-o-keyframes #{$name} { 27 | @content; 28 | } 29 | } 30 | @if $original-prefix-for-spec { 31 | @include disable-prefix-for-all(); 32 | $prefix-for-spec: true; 33 | @keyframes #{$name} { 34 | @content; 35 | } 36 | } 37 | 38 | $prefix-for-webkit: $original-prefix-for-webkit; 39 | $prefix-for-mozilla: $original-prefix-for-mozilla; 40 | $prefix-for-microsoft: $original-prefix-for-microsoft; 41 | $prefix-for-opera: $original-prefix-for-opera; 42 | $prefix-for-spec: $original-prefix-for-spec; 43 | } 44 | -------------------------------------------------------------------------------- /sass/_prefixer.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Example: @include prefixer(border-radius, $radii, webkit ms spec); 3 | //************************************************************************// 4 | $prefix-for-webkit: true !default; 5 | $prefix-for-mozilla: true !default; 6 | $prefix-for-microsoft: true !default; 7 | $prefix-for-opera: true !default; 8 | $prefix-for-spec: true !default; // required for keyframe mixin 9 | 10 | @mixin prefixer ($property, $value, $prefixes) { 11 | @each $prefix in $prefixes { 12 | @if $prefix == webkit { 13 | @if $prefix-for-webkit { 14 | -webkit-#{$property}: $value; 15 | } 16 | } 17 | @else if $prefix == moz { 18 | @if $prefix-for-mozilla { 19 | -moz-#{$property}: $value; 20 | } 21 | } 22 | @else if $prefix == ms { 23 | @if $prefix-for-microsoft { 24 | -ms-#{$property}: $value; 25 | } 26 | } 27 | @else if $prefix == o { 28 | @if $prefix-for-opera { 29 | -o-#{$property}: $value; 30 | } 31 | } 32 | @else if $prefix == spec { 33 | @if $prefix-for-spec { 34 | #{$property}: $value; 35 | } 36 | } 37 | @else { 38 | @warn "Unrecognized prefix: #{$prefix}"; 39 | } 40 | } 41 | } 42 | 43 | @mixin disable-prefix-for-all() { 44 | $prefix-for-webkit: false; 45 | $prefix-for-mozilla: false; 46 | $prefix-for-microsoft: false; 47 | $prefix-for-opera: false; 48 | $prefix-for-spec: false; 49 | } 50 | -------------------------------------------------------------------------------- /sass/_transform.scss: -------------------------------------------------------------------------------- 1 | @mixin transform($property: none) { 2 | // none | 3 | @include prefixer(transform, $property, webkit moz ms o spec); 4 | } 5 | 6 | @mixin transform-origin($axes: 50%) { 7 | // x-axis - left | center | right | length | % 8 | // y-axis - top | center | bottom | length | % 9 | // z-axis - length 10 | @include prefixer(transform-origin, $axes, webkit moz ms o spec); 11 | } 12 | 13 | @mixin transform-style ($style: flat) { 14 | @include prefixer(transform-style, $style, webkit moz ms o spec); 15 | } 16 | -------------------------------------------------------------------------------- /sass/_transition.scss: -------------------------------------------------------------------------------- 1 | // Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. 2 | // Example: @include transition (all, 2.0s, ease-in-out); 3 | // @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s)); 4 | // @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s)); 5 | 6 | @mixin transition ($properties...) { 7 | @if length($properties) >= 1 { 8 | @include prefixer(transition, $properties, webkit moz spec); 9 | } 10 | 11 | @else { 12 | $properties: all 0.15s ease-out 0; 13 | @include prefixer(transition, $properties, webkit moz spec); 14 | } 15 | } 16 | 17 | @mixin transition-property ($properties...) { 18 | -webkit-transition-property: transition-property-names($properties, 'webkit'); 19 | -moz-transition-property: transition-property-names($properties, 'moz'); 20 | transition-property: transition-property-names($properties, false); 21 | } 22 | 23 | @mixin transition-duration ($times...) { 24 | @include prefixer(transition-duration, $times, webkit moz spec); 25 | } 26 | 27 | @mixin transition-timing-function ($motions...) { 28 | // ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() 29 | @include prefixer(transition-timing-function, $motions, webkit moz spec); 30 | } 31 | 32 | @mixin transition-delay ($times...) { 33 | @include prefixer(transition-delay, $times, webkit moz spec); 34 | } -------------------------------------------------------------------------------- /sass/call.scss: -------------------------------------------------------------------------------- 1 | $active-color : green; 2 | $inactive-color : rgb(157, 38, 29); 3 | $join-call-button-width : 15em; 4 | $call-button-height : 5em; 5 | $call-button-width : 6em; 6 | 7 | .callClient { 8 | background: $teagreen; 9 | height:100%; 10 | width:100%; 11 | font-family:$pretty-font-family; 12 | .no-call, .in-call, .loading { 13 | position:absolute; 14 | top:0; 15 | left:0; 16 | width:100%; 17 | height:100%; 18 | } 19 | .icon-phone { 20 | &.active { 21 | color: $active-color; 22 | } 23 | &.inactive { 24 | color: $inactive-color; 25 | } 26 | } 27 | } 28 | 29 | .no-call { 30 | h1, h2 { 31 | border: none; 32 | } 33 | text-align:center; 34 | display:none; 35 | z-index:10; 36 | padding-top:2em; 37 | .icon { 38 | @include transition(all 1s ease-in-out); 39 | cursor:pointer; 40 | cursor:hand; 41 | display:block; 42 | font-size:6em; 43 | text-shadow:-2px -2px 0px lighten($teagreen, 10%); 44 | } 45 | p { 46 | font-size:1em; 47 | } 48 | 49 | .join-call-buttons { 50 | width:2 * $join-call-button-width; 51 | margin:auto; 52 | margin-top:1em; 53 | .btn { 54 | float:left; 55 | @include unselectable(); 56 | @include transition(all 0.5s ease-in-out); 57 | padding: 4em; 58 | border-radius: 5px; 59 | width: $join-call-button-width; 60 | margin: 0 auto 2em auto; 61 | &:hover { 62 | background: rgba(255, 255, 255, 0.4); 63 | } 64 | } 65 | } 66 | } 67 | 68 | .unmuted { 69 | .disabled-state { 70 | opacity:0; 71 | } 72 | } 73 | 74 | .webrtc-error { 75 | display:none; 76 | text-align:center; 77 | margin:auto; 78 | padding-top:5em; 79 | .reason { 80 | display:none; 81 | } 82 | .icon-stack { 83 | font-size:10em; 84 | } 85 | .base { 86 | color:#ccc; 87 | } 88 | } 89 | .in-call { 90 | display:none; 91 | } 92 | 93 | 94 | .call-controls { 95 | position:absolute; 96 | width:100%; 97 | bottom:0; 98 | .icon-ban-circle, .icon-phone { 99 | @include transition(all 0.5s ease-in-out); 100 | } 101 | .buttons.three { 102 | margin: auto; 103 | width: 3 * $call-button-width; 104 | height: $call-button-height; 105 | } 106 | button { 107 | @include unselectable(); 108 | @include transition(all 0.5s ease-in-out); 109 | box-shadow: -2px -2px 3px rgba(0,0,0,0.5); 110 | cursor:pointer; 111 | font-size:1em; 112 | display:block; 113 | height: $call-button-height; 114 | width: $call-button-width; 115 | float:left; 116 | background:#111; 117 | color:#aaa; 118 | border:0px; 119 | border-right:1px solid #1f1f1f; 120 | &:first-child { 121 | border-top-left-radius:10px; 122 | } 123 | &:last-child { 124 | border-top-right-radius:10px; 125 | border-right:0; 126 | } 127 | 128 | &:hover { 129 | background:#1f1f1f; 130 | .icon-phone { 131 | color:$inactive-color; 132 | } 133 | &.unmuted { 134 | .icon-ban-circle { // show post-click state on hover 135 | opacity:1; 136 | } 137 | } 138 | } 139 | } 140 | .mute-audio, .mute-video { 141 | > span { 142 | font-size: 1.8em; 143 | line-height: 1.8em; 144 | } 145 | } 146 | } 147 | 148 | .videos { 149 | width:100%; 150 | } 151 | .in-call { 152 | .videos { 153 | position:absolute; 154 | height:100%; 155 | width:100%; 156 | padding-bottom:$my-stream-height; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /sass/code.scss: -------------------------------------------------------------------------------- 1 | .ghost-cursor { 2 | position:absolute; 3 | width:1px; 4 | height:1em; 5 | box-shadow: 1px 1px 1px black; 6 | .user { 7 | position:absolute; 8 | top:13px; 9 | z-index:20; 10 | left:0; 11 | font-size:0.7em; 12 | color:inherit; 13 | background:black; 14 | } 15 | } 16 | 17 | #coding .codeClient { 18 | height:100%; 19 | background: $teagreen; 20 | overflow:hidden; 21 | .codelog { 22 | width:50%; 23 | float:left; 24 | } 25 | .replPANE { 26 | width:50%; 27 | float:right; 28 | word-break:break-word; 29 | position:relative; 30 | 31 | .repl-options { 32 | font-size:1em; 33 | height: $repl-options-height; 34 | position: relative; 35 | z-index:2; 36 | padding: 1em; 37 | .btn { 38 | margin-left: 0.5em; 39 | } 40 | } 41 | 42 | .jsREPL, .htmlREPL { 43 | height: 50%; 44 | width:100%; 45 | padding:1em; 46 | position: absolute; 47 | } 48 | .jsREPL { 49 | bottom:0; 50 | } 51 | .htmlREPL { 52 | top:0; 53 | padding-top: $repl-options-height; 54 | } 55 | iframe { 56 | background: white; 57 | height:90%; 58 | width:100%; 59 | border: 1px solid #999; 60 | } 61 | } 62 | 63 | .CodeMirror { 64 | background: #3d3d3d; 65 | width:100%; 66 | height:100%; 67 | } 68 | .CodeMirror-scroll { 69 | box-sizing:content-box; 70 | } 71 | & > div { 72 | height:100%; 73 | } 74 | .editor { 75 | width:100%; 76 | height:50%; 77 | position:relative; 78 | .explanation { 79 | position:relative; 80 | z-index:10; 81 | height:30px; 82 | font-family: $pretty-font-family; 83 | font-size: 1em; 84 | line-height: 1em; 85 | padding: 0.5em 2em; 86 | background: $teagreen; 87 | } 88 | .textarea_container { 89 | width:100%; 90 | height:100%; 91 | position:absolute; 92 | top:0; 93 | left:0; 94 | padding-top:30px; 95 | box-sizing:border-box; 96 | 97 | textarea { 98 | width:100%; 99 | height:100%; 100 | } 101 | } 102 | } 103 | .jsIframe { 104 | width:100%; 105 | height:50%; 106 | } 107 | } -------------------------------------------------------------------------------- /sass/combined.scss: -------------------------------------------------------------------------------- 1 | @import "_prefixer.scss"; 2 | @import "_transition.scss"; 3 | @import "_transform.scss"; 4 | @import "_keyframes.scss"; 5 | @import "_animation.scss"; 6 | @import "main"; 7 | @import "left-sidenav"; 8 | @import "support-bar"; 9 | @import "../vendor/fontawesome/scss/font-awesome.scss"; 10 | @import "tooltips"; 11 | @import "view_modifiers"; 12 | @import "modal"; 13 | @import "draw"; 14 | @import "code"; 15 | @import "call"; 16 | @import "codemirror"; 17 | @import "monokai"; 18 | @import "ui/spinner"; 19 | @import "drag-staging"; 20 | @import "mobile"; 21 | @import "ui/spectrum"; 22 | @import "ui/spectrum-overrides"; 23 | @import "ui/mewl"; 24 | -------------------------------------------------------------------------------- /sass/drag-staging.scss: -------------------------------------------------------------------------------- 1 | .drag-mask { 2 | position: absolute; 3 | top:0; 4 | left:0; 5 | width:100%; 6 | height:100%; 7 | z-index:99; 8 | display: none; 9 | } 10 | .drag-staging { 11 | font-family: $pretty-font-family; 12 | position: absolute; 13 | overflow: hidden; 14 | top:0; 15 | left:0; 16 | width:100%; 17 | height:100%; 18 | z-index:95; 19 | display: none; 20 | background:rgba(0,0,0,0.95); 21 | text-align: center; 22 | .staging-area { 23 | position:absolute; 24 | top:0; 25 | left:0; 26 | height:100%; 27 | width:100%; 28 | overflow:hidden; 29 | padding:1em; 30 | img { 31 | width:100%; 32 | height:auto; 33 | } 34 | } 35 | .file-name { 36 | color: white; 37 | } 38 | .description { 39 | margin-bottom: 1em; 40 | } 41 | } 42 | // buttons: 43 | .drag-staging .flex-buttons { 44 | position: absolute; 45 | bottom: 0; 46 | width: 100%; 47 | height: 3em; 48 | button { 49 | border-radius: 0; 50 | } 51 | } -------------------------------------------------------------------------------- /sass/draw.scss: -------------------------------------------------------------------------------- 1 | .swatch { 2 | width:20px; 3 | height:20px; 4 | display:inline-block; 5 | margin-left:3px; 6 | background:black; 7 | cursor:hand; 8 | box-shadow:1px 1px 1px rgba(0,0,0,0.75); 9 | } 10 | 11 | .drawingClient { 12 | @include unselectable; 13 | display:none; 14 | width:100%; 15 | height:100%; 16 | font-family: $pretty-font-family; 17 | 18 | .tool { 19 | display: block; 20 | position: relative; 21 | padding: 0.5em; 22 | margin-bottom: 0.5em; 23 | box-shadow: 1px 1px 0px #aaa; 24 | background: lighten($teagreen, 5%); 25 | 26 | &:hover { 27 | background: lighten($softblue, 5%); 28 | } 29 | &.tool-highlight { 30 | color: inherit; 31 | box-shadow: 1px 1px 0px #999; 32 | background: lighten($teagreen, 15%); 33 | } 34 | .tool-options { 35 | position:absolute; 36 | left:0; 37 | top:0; 38 | padding-left:25px; 39 | &.active { 40 | .tool-options-contents { 41 | width:900px; 42 | height:auto; 43 | opacity:1; 44 | } 45 | } 46 | } 47 | .tool-options-contents { 48 | padding:5px; 49 | @include transition(all 0.25s ease-in-out); 50 | text-align: left; 51 | background:rgba(0,0,0,0.55); 52 | width:0px; 53 | height:0px; 54 | opacity:0; 55 | overflow:hidden; 56 | } 57 | } 58 | 59 | .tools { 60 | position:absolute; 61 | height:100%; 62 | background: $teagreen; 63 | box-shadow: 1px 0px 0px #aaa; 64 | padding: 1em 0.5em; 65 | z-index:10; 66 | font-size:20px; 67 | text-align: center; 68 | .explanation { 69 | font-size:0.7em; 70 | } 71 | i { 72 | width:100%; 73 | display:block; 74 | } 75 | } 76 | .drawarea { 77 | position:absolute; 78 | width:100%; 79 | padding-left:25px; 80 | height:100%; 81 | .canvas-area { 82 | width:100%; 83 | height:100%; 84 | position:relative; 85 | } 86 | canvas { 87 | cursor:crosshair; 88 | &:focus, &:active { 89 | cursor:crosshair; 90 | } 91 | position:absolute; 92 | top:0; 93 | left:0; 94 | &.background { 95 | z-index:1; 96 | } 97 | &.foreground { 98 | z-index:2; 99 | } 100 | &.active{ 101 | z-index: 3; 102 | } 103 | } 104 | .paper { 105 | position:absolute; 106 | width:100%; 107 | height:100%; 108 | z-index:0; 109 | background:url(../noisy_grid.png) repeat; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /sass/embedded.scss: -------------------------------------------------------------------------------- 1 | @import "_prefixer.scss"; 2 | @import "_transition.scss"; 3 | @import "_transform.scss"; 4 | @import "_keyframes.scss"; 5 | @import "_animation.scss"; 6 | @import "view_modifiers"; 7 | 8 | $link: #3af; 9 | 10 | // a sensible box model that makes all of this possible 11 | * { 12 | box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | -webkit-box-sizing:border-box; 15 | } 16 | 17 | html, body { 18 | width: 100%; 19 | height: 100%; 20 | position: relative; 21 | overflow: hidden; 22 | margin: 0; 23 | padding: 0; 24 | font-family: sans-serif; 25 | font-size: 0.9em; 26 | } 27 | a { 28 | color: $link; 29 | } 30 | 31 | .github-nickname { 32 | display: none; 33 | font-size: 1.2em; 34 | } 35 | 36 | .pgp-armored-body { 37 | display: none; 38 | } 39 | .un-keyblock { 40 | display: none; 41 | } 42 | .message-options { 43 | display: none; 44 | } 45 | 46 | .unknown { 47 | color: orange; 48 | } 49 | .trusted { 50 | color: green; 51 | } 52 | .untrusted, .error, .errors { 53 | color: red; 54 | } 55 | 56 | i.sub-icon { 57 | top: 3px; 58 | left: 4px; 59 | } 60 | 61 | .pin-button, .supportbar, .not-encrypted, .channel-options, .growl { 62 | display: none; 63 | } 64 | .time { 65 | float:right; 66 | } 67 | .chatMessage { 68 | border-radius:5px; 69 | background: #efefef; 70 | padding:1em; 71 | margin:1em; 72 | &:nth-child(2n) { 73 | background: #f0f0f0; 74 | } 75 | } 76 | 77 | .messages-pane, .chatinput { 78 | position: absolute; 79 | } 80 | .chat-input-control, textarea { 81 | width: 100%; 82 | height:100%; 83 | } 84 | .chat-input-control { 85 | padding: 0.5em 1em; 86 | } 87 | textarea { 88 | resize: none; 89 | border: 1px solid #ccc; 90 | border-radius: 5px; 91 | min-width: 100%; 92 | max-width: 100%; 93 | min-height: 100%; 94 | max-height: 100%; 95 | width: 100%; 96 | height: 100%; 97 | } 98 | .messages-pane { 99 | width:100%; 100 | height:90%; 101 | } 102 | .chatChannel, .chat-panel, .channel, .chatarea { 103 | width: 100%; 104 | height: 100%; 105 | position: relative; 106 | } 107 | .messages { 108 | width: 100%; 109 | height: 100%; 110 | overflow-y: scroll; 111 | } 112 | .chatinput { 113 | width:100%; 114 | height:10%; 115 | bottom: 0; 116 | } 117 | .nick-decorators { 118 | float:left; 119 | padding-right: 0.5em; 120 | } 121 | .nick { 122 | text-shadow: 1px 1px 0px #ccc; 123 | } 124 | .time { 125 | color: #666; 126 | font-size:0.9em; 127 | } 128 | .chatMessage-edit { 129 | font-size: 0.8em; 130 | display: inline-block; 131 | cursor: pointer; 132 | &::after { 133 | content: "Edit message"; 134 | color: $link; 135 | cursor: pointer; 136 | text-decoration: underline; 137 | } 138 | } 139 | 140 | .body-content { 141 | word-wrap: break-word; 142 | } 143 | body.highlight_mine { 144 | .chatMessage.me { 145 | background: #dedede; 146 | } 147 | } 148 | 149 | .emoji { 150 | width: 20px; 151 | } 152 | 153 | .growl { 154 | display: none !important; 155 | } -------------------------------------------------------------------------------- /sass/left-sidenav.scss: -------------------------------------------------------------------------------- 1 | $nav-primary: #2A2C2B; 2 | $module-button-height: 3.5em; 3 | 4 | #module-buttons { 5 | z-index: 2; 6 | @include panel; 7 | top: 0; 8 | left: 0; 9 | background: darken($nav-primary, 10%); 10 | height: $module-button-height; 11 | .button { 12 | text-align: center; 13 | font-size: 1.3em; 14 | } 15 | .button-description { 16 | display: none; 17 | } 18 | } 19 | .tabButton.active { 20 | color: #0084FF; 21 | } 22 | 23 | #channel-switcher { 24 | @include panel; 25 | z-index: 1; 26 | padding-top: $module-button-height; 27 | padding-bottom: $module-button-height; 28 | } 29 | 30 | #global-options-buttons { 31 | z-index: 2; 32 | height: $module-button-height; 33 | position: absolute; 34 | bottom: 0; 35 | width: 100%; 36 | 37 | .button { 38 | font-size: 1.5em; 39 | } 40 | .button-description { 41 | display: none; 42 | } 43 | } 44 | 45 | .functionality { 46 | @include unselectable; 47 | @include panel; 48 | width: $sidenav-width; 49 | background: $nav-primary; 50 | box-shadow: 1px 0px 1px #333; 51 | z-index: 120; 52 | button { 53 | background: transparent; 54 | text-align: left; 55 | color: lighten($nav-primary, 25%); 56 | } 57 | } 58 | 59 | .channel-button { 60 | position: relative; 61 | box-sizing: content-box; // Firefox has a bug when there's white-space: nowrap inside of a fucking border-box! 62 | .channel-button-content { 63 | padding: 0.5em 2em; 64 | } 65 | .activity-indicator { 66 | display: none; 67 | position: absolute; 68 | width: 4px; 69 | height: 100%; 70 | left: 0; 71 | top: 0; 72 | background: green; 73 | } 74 | &.activity .activity-indicator { 75 | display: block; 76 | } 77 | } 78 | 79 | .channel-switcher-contents { 80 | height: 100%; 81 | width: 100%; 82 | overflow-x: hidden; 83 | overflow-y: auto; 84 | .button { 85 | width: 100%; 86 | font-size: 1.3em; 87 | .channel-button-content { 88 | position: relative; 89 | width: 100%; 90 | height: 100%; 91 | } 92 | .channel-name, i { 93 | display: block; 94 | color: lighten($nav-primary, 50%); 95 | } 96 | .close { 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | width: 1.2em; 101 | line-height: 1.3em; 102 | height: 100%; 103 | text-align: center; 104 | color: lighten($nav-primary, 15%); 105 | text-shadow: 1px 1px 0px darken($nav-primary, 10%); 106 | } 107 | } 108 | .button.active { 109 | background: lighten($nav-primary, 5%); 110 | } 111 | .channel-button-body { 112 | width: 100%; 113 | } 114 | .topic, .users { 115 | font-size: 0.9em; 116 | width: 100%; 117 | display: block; 118 | color: lighten($nav-primary, 30%); 119 | } 120 | .topic { 121 | font-size:0.7em; 122 | max-height: 1.5em; // don't use height, because it looks weird when there is no topic 123 | overflow: hidden; 124 | text-overflow: ellipsis; 125 | white-space: nowrap; 126 | } 127 | .users { 128 | color: #686868; 129 | margin-top: 0.5em; 130 | i { 131 | color: inherit; 132 | display: inline-block; 133 | } 134 | .active { 135 | color: $active-color; 136 | } 137 | } 138 | .button.active { 139 | .topic { 140 | max-height: 100%; 141 | white-space: normal; 142 | color: lighten($nav-primary, 40%); 143 | } 144 | } 145 | } 146 | 147 | .channels .button { 148 | border-bottom: 1px solid lighten($nav-primary, 5%); 149 | } 150 | 151 | .join-channel button { 152 | height: 2em; 153 | font-size: 1.2em; 154 | text-align: center; 155 | } 156 | .join-channel input { 157 | display: none; // toggles on and off, initial is off 158 | } 159 | -------------------------------------------------------------------------------- /sass/mobile.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 600px) { 2 | .supportbar { 3 | display: none; 4 | } 5 | .chatarea { 6 | padding-right: 0px; 7 | } 8 | .pin-button { 9 | display: none; 10 | } 11 | 12 | #module-buttons { 13 | display: none; 14 | } 15 | #channel-switcher { 16 | padding-top: 0; 17 | } 18 | 19 | #main { 20 | padding-left: 0; 21 | padding-top: 0; 22 | } 23 | #chatting { 24 | .messages-pane { 25 | padding-bottom: 3em; 26 | } 27 | .chatinput { 28 | height: 3em; 29 | padding: 1em; 30 | } 31 | .channel-options { 32 | display: none; 33 | } 34 | 35 | .chatMessage { 36 | padding: 1em 0.5em; 37 | margin-top: 0.5em; 38 | } 39 | .nick, .body, .time { 40 | position: static; 41 | display: inline; 42 | padding: 0; 43 | margin: 0.5em; 44 | width: auto; 45 | } 46 | .time { 47 | float: right; 48 | } 49 | .body { 50 | display: block; 51 | } 52 | } 53 | 54 | nav.functionality { 55 | width: 100%; 56 | left: 0%; 57 | @include transition(all 0.5s ease-in-out); 58 | &.inactive { 59 | @include transition(all 0.5s ease-in-out); 60 | left: -100%; 61 | } 62 | } 63 | 64 | header, .channelSwitcher, .channel-selector { 65 | width: 100%; 66 | height: 100%; 67 | position: absolute; 68 | } 69 | header { 70 | .channel-selector { 71 | padding:0; 72 | width:100%; 73 | height:100%; 74 | top:0; 75 | left:0; 76 | .channels { 77 | width:100%; 78 | background:#111; 79 | overflow-y:scroll; 80 | padding-bottom:12em; 81 | .channelBtn { 82 | padding-left: 0; 83 | } 84 | } 85 | .channelBtn { 86 | float: none; 87 | height: auto; 88 | margin: 0; 89 | font-size:1em; 90 | padding:1em; 91 | border: 0; 92 | outline: 0; 93 | width:100%; 94 | &:hover, &.active { 95 | border: 0; 96 | outline: 0; 97 | } 98 | .channelName { 99 | height:auto; 100 | } 101 | .close { 102 | float:none; 103 | position: absolute; 104 | top: 0; 105 | right: 0; 106 | line-height: 2.5em; 107 | } 108 | } 109 | .join-channel { 110 | height:auto; 111 | width:100%; 112 | margin:0; 113 | .channelBtn { 114 | background:#333; 115 | } 116 | .channelName { 117 | float:none; 118 | margin:auto; 119 | } 120 | } 121 | } 122 | } 123 | 124 | .join-channel { 125 | position: absolute; 126 | bottom:0; 127 | left:0; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /sass/modal.scss: -------------------------------------------------------------------------------- 1 | .backdrop { 2 | @include panel; 3 | z-index: 1000; 4 | background: rgba(0,0,0,0.75); 5 | display: flex; 6 | align-items: center; 7 | } 8 | 9 | .modal { 10 | font-family: sans-serif; 11 | position:relative; 12 | width: 32em; 13 | background: $white; 14 | color: inherit; 15 | box-shadow:inset -2px -2px 0px -1px rgba(155,225,255,0.1); 16 | padding:2em; 17 | overflow:hidden; 18 | z-index:2; 19 | 20 | h1, h2 { 21 | font-family: inherit; 22 | margin-top: 0; 23 | } 24 | 25 | textarea { 26 | font-size: 0.9em; 27 | } 28 | 29 | .close-button { 30 | @include unselectable; 31 | position: absolute; 32 | top: 0; 33 | right: 0; 34 | z-index: 2; 35 | background: inherit; 36 | } 37 | 38 | &.centered { 39 | margin:auto; 40 | } 41 | 42 | > .field { 43 | position:relative; 44 | z-index:2; 45 | } 46 | 47 | .field { 48 | label { 49 | display:inline-block; 50 | width:auto; 51 | margin-bottom: 0.5em 52 | } 53 | label.description { 54 | font-size:0.9em; 55 | float:right; 56 | color: #777; 57 | } 58 | textarea { 59 | width: 100%; 60 | height: 6em; 61 | display: block; 62 | resize: none; 63 | } 64 | } 65 | 66 | .user-emblem { 67 | @include unselectable(); 68 | @include panel; 69 | z-index:1; 70 | font-size: 300px; 71 | color: #1a1a1a; 72 | text-align: center; 73 | } 74 | 75 | section { 76 | display: none; 77 | &.active { 78 | display: block; 79 | } 80 | } 81 | .field { 82 | margin:1em 0 ; 83 | } 84 | input[type='text'], input[type='password'] { 85 | width: 100%; 86 | padding: 0.5em; 87 | } 88 | button { 89 | text-transform: none; 90 | } 91 | } 92 | 93 | .crypto-key-popup { 94 | z-index: 2; 95 | .anti-surveillance { 96 | @include unselectable(); 97 | float:left; 98 | margin:0 0.25em 0.25em 0; 99 | font-size: 6em; 100 | color: #1a1a1a; 101 | text-align: center; 102 | .fa-eye-slash { 103 | color: #F00; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /sass/monokai.scss: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} 4 | .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} 5 | .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} 6 | .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;} 7 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} 8 | 9 | .cm-s-monokai span.cm-comment {color: #75715e;} 10 | .cm-s-monokai span.cm-atom {color: #ae81ff;} 11 | .cm-s-monokai span.cm-number {color: #ae81ff;} 12 | 13 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} 14 | .cm-s-monokai span.cm-keyword {color: #f92672;} 15 | .cm-s-monokai span.cm-string {color: #e6db74;} 16 | 17 | .cm-s-monokai span.cm-variable {color: #a6e22e;} 18 | .cm-s-monokai span.cm-variable-2 {color: #9effff;} 19 | .cm-s-monokai span.cm-def {color: #fd971f;} 20 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} 21 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;} 22 | .cm-s-monokai span.cm-tag {color: #f92672;} 23 | .cm-s-monokai span.cm-link {color: #ae81ff;} 24 | 25 | .cm-s-monokai .CodeMirror-matchingbracket { 26 | text-decoration: underline; 27 | color: white !important; 28 | } -------------------------------------------------------------------------------- /sass/tooltips.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | z-index:1000; 3 | font-family: $pretty-font-family; 4 | color: white; 5 | $title-color: black; 6 | $arrow-width:10px; 7 | $arrow-height:5px; 8 | position: absolute; 9 | background: black; 10 | &.danger, &.error { 11 | background: $error; 12 | } 13 | &.success { 14 | background: $success; 15 | } 16 | color: white; 17 | margin-top:10px; 18 | width: 16em; 19 | font-size: 0.9em; 20 | border-radius: 0.5em; 21 | margin-left: -14em; 22 | text-align: center; 23 | 24 | &.from-left { 25 | margin-left: 0; 26 | } 27 | 28 | .title { 29 | padding: 0.25em; 30 | background: $title-color; 31 | border-top-left-radius: 1em; 32 | border-top-right-radius: 1em; 33 | font-weight:bold; 34 | border-top-right-radius: 0.5em; 35 | border-top-left-radius: 0.5em; 36 | } 37 | .body { 38 | font-size:0.9em; 39 | margin: 0.5em 0.85em 0.7em 0.85em; 40 | word-break: break-word; 41 | } 42 | &::before { 43 | /* upward arrow */ 44 | position: absolute; 45 | content: ""; 46 | border-left: $arrow-width solid transparent; 47 | border-right: $arrow-width solid transparent; 48 | border-bottom: $arrow-height solid $title-color; 49 | border-top: none; 50 | width: 0px; 51 | height: 0px; 52 | box-sizing: content-box; 53 | top: -$arrow-height; 54 | right: 1em; 55 | margin-left: -$arrow-width; 56 | border-style: inset !important; 57 | } 58 | &.on-top::before { 59 | /* downward arrow */ 60 | position: absolute; 61 | content: ""; 62 | border-left: $arrow-width solid transparent; 63 | border-right: $arrow-width solid transparent; 64 | border-top: $arrow-height solid $title-color; 65 | border-bottom: none; 66 | width: 0px; 67 | height: 0px; 68 | box-sizing: content-box; 69 | top: 100%; 70 | right: 1em; 71 | margin-left: -$arrow-width; 72 | } 73 | &.from-left::before, &.on-top.from-left::before { 74 | right: 14em; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /sass/ui/mewl.scss: -------------------------------------------------------------------------------- 1 | @mixin radial-gradient($gradient...) { 2 | background-image: -webkit-radial-gradient($gradient); 3 | background-image: -moz-radial-gradient($gradient); 4 | background-image: -o-radial-gradient($gradient); 5 | background-image: radial-gradient($gradient); 6 | } 7 | @mixin linear-gradient($gradientLine, $colorStops...) { 8 | background-image: -webkit-linear-gradient($gradientLine, $colorStops); 9 | background-image: -moz-linear-gradient($gradientLine, $colorStops); 10 | background-image: -o-linear-gradient($gradientLine, $colorStops); 11 | @if length($gradientLine) == 2 { 12 | background-image: linear-gradient(to #{inverse-side(nth($gradientLine, 1))} #{inverse-side(nth($gradientLine, 2))}, $colorStops); 13 | } @else { 14 | background-image: linear-gradient(to #{inverse-side($gradientLine)}, $colorStops); 15 | } 16 | } 17 | 18 | 19 | /* 20 | * Copyright (c) 2012-2013 Thibaut Courouble 21 | * http://www.cssflow.com 22 | * 23 | * Licensed under the MIT License: 24 | * http://www.opensource.org/licenses/mit-license.php 25 | */ 26 | 27 | $mewl-padding: 0.5em; 28 | .growl { 29 | @include transition(all 0.5s ease-in-out); 30 | display:block; 31 | opacity:0; 32 | position:absolute; 33 | min-width:10em; 34 | max-width:40em; 35 | z-index:10000; 36 | font-size:0.95em; 37 | overflow:hidden; 38 | font-family: $pretty-font-family; 39 | border-radius:10px; 40 | padding: 3px; 41 | 42 | // positioning: 43 | &.top { 44 | top:1em; 45 | padding-bottom: $mewl-padding; 46 | } 47 | &.bottom { 48 | bottom:1em; 49 | padding-top: $mewl-padding; 50 | } 51 | &.left { 52 | left:1em; 53 | padding-right: $mewl-padding; 54 | } 55 | &.right { 56 | right:1em; 57 | padding-left: $mewl-padding; 58 | } 59 | 60 | // actually show it 61 | &.shown { 62 | opacity:1; 63 | } 64 | &.slide-right { 65 | right: -5em; 66 | opacity: 0; 67 | } 68 | } 69 | 70 | .notif { 71 | position: relative; 72 | width: 288px; 73 | padding: 0.5em 1em; 74 | color: rgba(#ccc, .9); 75 | text-shadow: 1px 1px rgba(black, 0.3); 76 | border: 1px solid; 77 | border-color: rgba(#111, .6) rgba(#111, .7) rgba(#111, .9); 78 | border-radius: 5px; 79 | box-shadow: inset 0 0 1px rgba(white, .5), 80 | 0 1px 2px rgba(black, .3); 81 | 82 | p { margin: 0; } 83 | 84 | a { 85 | font-weight: bold; 86 | color: rgba(#ddd, .9); 87 | text-decoration: none; 88 | } 89 | 90 | .action-btn { 91 | position: absolute; 92 | top: 0; 93 | right: 0; 94 | border-bottom-right-radius: 0; 95 | border-top-left-radius: 0; 96 | } 97 | } 98 | 99 | .notif-title { 100 | margin: 0; 101 | line-height: 22px; 102 | font-size: 13px; 103 | font-weight: bold; 104 | color: rgba(white, .95); 105 | } 106 | 107 | .notif-white { 108 | color: rgba(#333, .9); 109 | text-shadow: 0 1px rgba(white, .2); 110 | background-color: rgba(white, .9); 111 | border-color: rgba(#444, .6) rgba(#444, .7) rgba(#444, .8); 112 | /* rgba(white, .4), rgba(white, .33) 50%, rgba(#eaeaea, .3) 50%, rgba(#eaeaea, .35) 100% */ 113 | @include linear-gradient(top, 114 | rgba(white, .55), 115 | rgba(white, .45) 50%, 116 | rgba(#dedede, .45) 50%, 117 | rgba(#dedede, .55)); 118 | // box-shadow: inset 0 0 1px rgba(white, .7), 119 | // 0 1px 2px rgba(black, .2); 120 | 121 | &:before { 122 | background-color: rgba(white, .1); 123 | border-right-color: rgba(#333, .3); 124 | box-shadow: 1px 0 rgba(white, .15); 125 | } 126 | 127 | .notif-title { color: rgba(black, .8); } 128 | 129 | a { color: rgba(#282828, .9); } 130 | } 131 | -------------------------------------------------------------------------------- /sass/ui/spectrum-overrides.scss: -------------------------------------------------------------------------------- 1 | // echoplexus overrides to default spectrum stylesheet 2 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { 3 | border-color: darken($teagreen, 50%); 4 | } 5 | .sp-replacer { 6 | border: 0; 7 | background: transparent; 8 | } 9 | .sp-input-container { 10 | margin-bottom: 0; 11 | } 12 | .sp-container { 13 | border-color: #999; 14 | } 15 | .sp-container input { 16 | border: 1px solid #999; 17 | } -------------------------------------------------------------------------------- /sass/ui/spinner.scss: -------------------------------------------------------------------------------- 1 | /* 2 | stolen from (cs)spinner.css - http://jh3y.github.io/-cs-spinner and modified 3 | Licensed under the MIT license 4 | 5 | Jhey Tompkins (c) 2014. 6 | 7 | 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: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | 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. 12 | */ 13 | 14 | $height: 10px; 15 | $width: 10px; 16 | $border-width: 3px; 17 | $base-color: #555; 18 | 19 | 20 | /* BASE */ 21 | .csspinner { 22 | position: relative; 23 | width: 0; 24 | height: 0; 25 | display: block; 26 | } 27 | .csspinner:before { 28 | content: ""; 29 | z-index: 1; 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | display: block; 34 | height: 100%; 35 | width: 100%; 36 | opacity: 0.6; 37 | } 38 | .csspinner:after { 39 | z-index: 2; 40 | content: ""; 41 | height: $height; 42 | width: $width; 43 | position: absolute; 44 | top: 50%; 45 | left: 50%; 46 | // margin: (-$width / 2) 0 0 (-$width / 2); 47 | transition: all .75s ease 0s; 48 | -webkit-transition: all .75s ease 0s; 49 | border-radius: 100%; 50 | border-top: $border-width solid $base-color; 51 | @include animation(standard .75s infinite linear); 52 | } 53 | .csspinner.no-overlay:before { 54 | content: none; 55 | display: none; 56 | } 57 | 58 | /* STANDARD ROTATION ANIMATION */ 59 | @include keyframes(standard) { 60 | from { 61 | -webkit-transform: rotate(0deg); 62 | } 63 | to { 64 | -webkit-transform: rotate(360deg); 65 | } 66 | } 67 | 68 | /* DOUBLE UP*/ 69 | $double-up-color-one: darken($teagreen, 40%); 70 | 71 | .csspinner.double-up:after { 72 | border-right: $border-width solid $double-up-color-one; 73 | border-top: $border-width double $double-up-color-one; 74 | border-left: $border-width double $double-up-color-one; 75 | border-bottom: $border-width double $double-up-color-one; 76 | } 77 | 78 | .csspinner.block { 79 | display: block; 80 | width: 2em; 81 | height: 2em; 82 | } 83 | .csspinner.center { 84 | position: relative; 85 | left: 50%; 86 | margin-left: -1em; 87 | } 88 | .csspinner.block.double-up:after { 89 | width: 100%; 90 | height: 100%; 91 | left: -25%; 92 | top: -25%; 93 | } -------------------------------------------------------------------------------- /sass/view_modifiers.scss: -------------------------------------------------------------------------------- 1 | $private : rgba(0,30,0,0.17); 2 | 3 | body { 4 | &.suppress_identity_acknowledgements { 5 | .chatMessage.identity.ack { 6 | display:none; 7 | } 8 | } 9 | &.suppress_join { 10 | .chatMessage.join, .chatMessage.part { 11 | display:none; 12 | } 13 | } 14 | &.highlight_mine { 15 | .chatMessage.me { 16 | background: rgba(255, 251, 231, 1); 17 | } 18 | .chatMessage.private { 19 | background: $private; 20 | } 21 | } 22 | &.suppress_client { 23 | .chatMessage.client{ 24 | display:none; 25 | } 26 | } 27 | &.show_mewl { 28 | .growl { 29 | display: block; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/CryptoWrapper.coffee: -------------------------------------------------------------------------------- 1 | module.exports.CryptoWrapper = class CryptoWrapper 2 | 3 | JsonFormatter: 4 | stringify: (cipherParams) -> 5 | 6 | # create json object with ciphertext 7 | jsonObj = ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64) 8 | 9 | # optionally add iv and salt 10 | jsonObj.iv = cipherParams.iv.toString() if cipherParams.iv 11 | jsonObj.s = cipherParams.salt.toString() if cipherParams.salt 12 | 13 | # stringify json object 14 | JSON.stringify jsonObj 15 | 16 | parse: (jsonStr) -> 17 | 18 | # parse json string 19 | jsonObj = JSON.parse(jsonStr) 20 | 21 | # extract ciphertext from json object, and create cipher params object 22 | cipherParams = CryptoJS.lib.CipherParams.create(ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct)) 23 | 24 | # optionally extract iv and salt 25 | cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv) if jsonObj.iv 26 | cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s) if jsonObj.s 27 | cipherParams 28 | 29 | encryptObject: (plaintextObj, key) -> 30 | throw "encryptObject: missing a parameter." if typeof plaintextObj is "undefined" or typeof key is "undefined" or key is "" 31 | # CryptoJS only takes strings 32 | plaintextObj = JSON.stringify(plaintextObj) if typeof plaintextObj is "object" 33 | enciphered = CryptoJS.AES.encrypt(plaintextObj, key, 34 | format: @JsonFormatter 35 | ) 36 | JSON.parse enciphered.toString() # format it back into an object for sending over socket.io 37 | 38 | decryptObject: (encipheredObj, key) -> 39 | throw "No enciphered object supplied" if !encipheredObj 40 | throw "No key supplied" if !key 41 | 42 | # attempt to decrypt the result: 43 | try 44 | decipheredObj = CryptoJS.AES.decrypt(JSON.stringify(encipheredObj), key, 45 | format: @JsonFormatter 46 | ) 47 | decipheredString = decipheredObj.toString(CryptoJS.enc.Utf8) 48 | catch e # if it fails nastily, output the ciphertext 49 | throw e 50 | -------------------------------------------------------------------------------- /src/client/PermissionModel.coffee: -------------------------------------------------------------------------------- 1 | module.exports.PermissionModel = class PermissionModel extends Backbone.Model 2 | 3 | defaults: 4 | canSetTopic: null # null represents no particular privilege or inhibition 5 | canMakePrivate: null 6 | canMakePublic: null 7 | canKick: null 8 | canMute: null 9 | canBan: null 10 | canSpeak: null 11 | canPullLogs: null 12 | canUploadFile: null 13 | canDeleteLogs: null 14 | canSetGithubPostReceiveHooks: null 15 | 16 | canBestow: null # eventually a map of bestowable permissions 17 | initialize: (modelAttributes, options) -> 18 | 19 | -------------------------------------------------------------------------------- /src/client/bootstrap.core.js.coffee: -------------------------------------------------------------------------------- 1 | # A set of common things, global functions, global objects that will 2 | # probably be used across ALL clients (desktop, mobile, embedded) 3 | module.exports.core = -> 4 | 5 | # attempt to determine their browsing environment 6 | ua = window.ua = 7 | firefox: !!navigator.mozConnection 8 | chrome: !!window.chrome 9 | 10 | if Storage # extend the local storage protoype if it exists 11 | Storage::setObj = (key, obj) -> 12 | localStorage.setItem key, JSON.stringify(obj) 13 | Storage::getObj = (key) -> 14 | JSON.parse localStorage.getItem(key) 15 | 16 | openpgp.initWorker('js/openpgp.worker.min.js') 17 | 18 | Notifications = require("./ui/Notifications.js.coffee").Notifications 19 | Faviconizer = require("./ui/Faviconizer.js.coffee").Faviconizer 20 | Options = require("./options.js.coffee").Options 21 | Keystore = require("./keystore.js.coffee").Keystore 22 | 23 | require("./visibility.js.coffee") 24 | require("./events.js.coffee")() 25 | 26 | window.visibility_status = "visible" 27 | VisibilityManager.onChange (visibility) -> 28 | window.visibility_status = visibility # duplicate the visibility status as a property we can look up at any time 29 | 30 | # these should really be refactored: 31 | window.codingModeActive = -> 32 | $("#coding").is ":visible" 33 | window.chatModeActive = -> 34 | $("#chatting").is ":visible" 35 | 36 | window.turnOffLiveReload = -> 37 | $(".livereload").attr "checked", false 38 | 39 | 40 | # global state / components needed for application 41 | window.GlobalUIState = new Backbone.Model 42 | chatIsPinned: false 43 | 44 | window.faviconizer = new Faviconizer() 45 | window.notifications = new Notifications() 46 | 47 | # Set cookie options 48 | # 14 seems like a good time to keep the cookie around 49 | window.COOKIE_OPTIONS = 50 | path: "/" 51 | expires: 14 52 | secure: (window.location.protocol == "https:") 53 | 54 | window.KEYSTORE = new Keystore() 55 | window.SOCKET_HOST = window.location.origin 56 | options = new Options() 57 | -------------------------------------------------------------------------------- /src/client/config.coffee: -------------------------------------------------------------------------------- 1 | module.exports.Modules =[ 2 | { 3 | name: 'chat', 4 | icon: 'fa-comments-o', 5 | title: 'Chat', 6 | section: 'chatting', 7 | active: true 8 | }, 9 | { 10 | name: 'code', 11 | icon: 'fa-code', 12 | title: 'Code', 13 | section: 'coding' 14 | }, 15 | { 16 | name: 'draw', 17 | icon: 'fa-pencil-square-o', 18 | title: 'Draw', 19 | section: 'drawing' 20 | }, 21 | { 22 | name:'call', 23 | icon: 'fa-phone', 24 | title: 'Call', 25 | section: 'calling' 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /src/client/events.js.coffee: -------------------------------------------------------------------------------- 1 | #TODO: more namespacing 2 | module.exports = -> 3 | window.events = _.clone(Backbone.Events) 4 | -------------------------------------------------------------------------------- /src/client/keystore.js.coffee: -------------------------------------------------------------------------------- 1 | module.exports.Keystore = class Keystore 2 | # dictionary of fingerprint -> key meta info 3 | # - trusted 4 | # - trusted_at (time) 5 | # - untrusted 6 | # - untrusted_at (time) 7 | # - last_used_by (nickname) 8 | # - last_used_at (time) 9 | # - last_used_in (channel) 10 | 11 | constructor: (opts) -> 12 | _.bindAll.apply(_, [this].concat(_.functions(this))) 13 | @keystore = localStorage.getObj("keystore") || {} 14 | 15 | add: (fingerprint, armored_key, armored_private_key, nick, channel) -> 16 | if !@keystore[fingerprint] 17 | @keystore[fingerprint] = 18 | armored_key: armored_key 19 | armored_private_key: armored_private_key 20 | last_used_by: nick 21 | first_used_at: (new Date()).getTime() 22 | last_used_at: (new Date()).getTime() 23 | last_used_in: channel 24 | trusted: false 25 | @save() 26 | else 27 | @markSeen(fingerprint, nick, channel) 28 | 29 | get: (fingerprint) -> 30 | return @keystore[fingerprint] 31 | 32 | clean: (fingerprint) -> 33 | @keystore[fingerprint].armored_private_key = null 34 | @save() 35 | 36 | markSeen: (fingerprint, nick, channel) -> 37 | return if !@keystore[fingerprint] 38 | @keystore[fingerprint].last_used_at = (new Date()).getTime() 39 | @keystore[fingerprint].last_used_by = nick 40 | @keystore[fingerprint].last_used_in = channel 41 | @save() 42 | 43 | is_trusted: (fingerprint) -> 44 | return @keystore[fingerprint]?.trusted 45 | 46 | is_untrusted: (fingerprint) -> 47 | return @keystore[fingerprint]?.untrusted 48 | 49 | trust_status: (fingerprint) -> 50 | return "trusted" if @is_trusted(fingerprint) 51 | return "untrusted" if @is_untrusted(fingerprint) 52 | return "unknown" 53 | 54 | trust: (fingerprint) -> 55 | @keystore[fingerprint].trusted = true 56 | @keystore[fingerprint].untrusted = false 57 | @keystore[fingerprint].trusted_at = (new Date()).getTime() 58 | @save() 59 | 60 | untrust: (fingerprint) -> 61 | @keystore[fingerprint].trusted = false 62 | @keystore[fingerprint].untrusted = true 63 | @keystore[fingerprint].untrusted_at = (new Date()).getTime() 64 | @save() 65 | 66 | neutral: (fingerprint) -> 67 | @keystore[fingerprint].trusted = false 68 | @keystore[fingerprint].untrusted = false 69 | @save() 70 | 71 | list: -> 72 | @keystore 73 | 74 | save: -> 75 | localStorage.setObj "keystore", @keystore 76 | -------------------------------------------------------------------------------- /src/client/loader.js.coffee: -------------------------------------------------------------------------------- 1 | defined_modules = require('./config.coffee').Modules 2 | 3 | module.exports.Loader = class Loader 4 | 5 | 6 | constructor: -> 7 | @modules = [] 8 | section = _.template($("#sectionTemplate").html()) 9 | button = _.template($("#buttonTemplate").html()) 10 | 11 | _.each defined_modules, (val) => 12 | val = _.defaults val, active: false 13 | s = $(section(val)).appendTo($("#panes")) 14 | s.hide() unless val.active 15 | $(button(val)).appendTo $("#module-buttons") 16 | @modules.push _.extend(val, 17 | view: "modules/" + val.name + "/client" 18 | ) 19 | 20 | @modules 21 | -------------------------------------------------------------------------------- /src/client/main.js.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq99/echoplexus/9257dfae99c9d847c7290704d20cfb12ad4f2f91/src/client/main.js.coffee -------------------------------------------------------------------------------- /src/client/modules/call/templates/callPanel.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |

You aren't authenticated to talk in this channel.

8 |

Move along, nothing to see here.

9 |
10 |
11 |

It seems your browser doesn't support WebRTC

12 |

Please try a new version of Chrome or Firefox Beta.

13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |

Call in progress.

22 |

Join in!

23 |
24 |
25 | 26 |

Video + Audio

27 |
28 |
29 | 30 |

Audio Only

31 |
32 |
33 |
34 |
35 |

No call in progress.

36 |

Start one?

37 |
38 |
39 | 40 |

Video + Audio

41 |
42 |
43 | 44 |

Audio Only

45 |
46 |
47 |
48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 | 56 |
57 |
58 | 63 | 69 | 75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /src/client/modules/call/templates/mediaStreamContainer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
<%- nick %>
4 | 5 |
6 |
-------------------------------------------------------------------------------- /src/client/modules/chat/Autocomplete.js.coffee: -------------------------------------------------------------------------------- 1 | # object: given a string A, returns a string B iff A is a substring of B 2 | # transforms A,B -> lowerCase for the comparison 3 | # TODO: use a scheme involving something like l-distance instead 4 | 5 | module.exports.Autocomplete = class Autocomplete 6 | constructor: -> 7 | @pool = [] # client names to select from 8 | @candidates = [] #The candidates 9 | @result = null #The last result 10 | @nID = 0 #The last NID 11 | 12 | setPool: (arr) -> 13 | @pool = _.uniq(arr) # e.g., don't bother having "Anonymous" in the pool twice 14 | @candidates = @pool 15 | @result = "" 16 | @nID = 0 17 | 18 | next: (stub) -> 19 | return "" unless @pool.length 20 | stub = stub.toLowerCase() # transform the stub -> lcase 21 | if stub is "" or stub is @result.toLowerCase() 22 | 23 | # scroll around the memoized array of candidates: 24 | @nID += 1 25 | @nID %= @candidates.length 26 | @result = "@" + @candidates[@nID] 27 | else # update memoized candidates 28 | @nID = 0 # start with the highest score (at pos 0) 29 | @candidates = _.chain(@pool).sortBy((n) => 30 | levDist stub, n.substring(0, stub.length).toLowerCase() 31 | ).value() 32 | 33 | # pick the closest match 34 | @result = "@" + @candidates[0] 35 | @result 36 | 37 | #http://www.merriampark.com/ld.htm, http://www.mgilleland.com/ld/ldjavascript.htm, Damerau–Levenshtein distance (Wikipedia) 38 | levDist = (s, t) -> 39 | d = [] #2d matrix 40 | 41 | # Step 1 42 | n = s.length 43 | m = t.length 44 | return m if n is 0 45 | return n if m is 0 46 | 47 | #Create an array of arrays in javascript (a descending loop is quicker) 48 | i = n 49 | 50 | while i >= 0 51 | d[i] = [] 52 | i-- 53 | 54 | # Step 2 55 | i = n 56 | 57 | while i >= 0 58 | d[i][0] = i 59 | i-- 60 | j = m 61 | 62 | while j >= 0 63 | d[0][j] = j 64 | j-- 65 | 66 | # Step 3 67 | i = 1 68 | 69 | while i <= n 70 | s_i = s.charAt(i - 1) 71 | 72 | # Step 4 73 | j = 1 74 | 75 | while j <= m 76 | 77 | #Check the jagged ld total so far 78 | return n if i is j and d[i][j] > 4 79 | t_j = t.charAt(j - 1) 80 | cost = (if (s_i is t_j) then 0 else 1) # Step 5 81 | 82 | #Calculate the minimum 83 | mi = d[i - 1][j] + 1 84 | b = d[i][j - 1] + 1 85 | c = d[i - 1][j - 1] + cost 86 | mi = b if b < mi 87 | mi = c if c < mi 88 | d[i][j] = mi # Step 6 89 | 90 | #Damerau transposition 91 | d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost) if i > 1 and j > 1 and s_i is t.charAt(j - 2) and s.charAt(i - 2) is t_j 92 | j++ 93 | i++ 94 | 95 | # Step 7 96 | d[n][m] 97 | -------------------------------------------------------------------------------- /src/client/modules/chat/Scrollback.js.coffee: -------------------------------------------------------------------------------- 1 | # object: a stack-like data structure supporting only: 2 | # - an index representing the currently looked-at element 3 | # - peeking at an element before/after the current stack pointer, and modifying that pointer 4 | # - adding new elements to the top of the stack 5 | # - emptying the stack 6 | # - replacing an element in the stack with another element 7 | 8 | module.exports.Scrollback = class Scrollback 9 | 10 | constructor: -> 11 | @buffer = [] 12 | @position = 0 13 | 14 | add: (userInput) -> 15 | @buffer.push userInput 16 | @position += 1 17 | 18 | prev: -> 19 | @position -= 1 if @position > 0 20 | @buffer[@position] 21 | 22 | next: -> 23 | @position += 1 if @position < @buffer.length 24 | @buffer[@position] 25 | 26 | replace: (prevObj, newObj) -> 27 | start = @buffer.length - 1 # start at the end of the array as this will be the most common case 28 | i = start 29 | 30 | while i >= 0 31 | if @buffer[i] is prevObj 32 | @buffer[i] = newObj 33 | return true 34 | i-- 35 | false # no match 36 | 37 | reset: -> 38 | @position = @buffer.length 39 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/channelCryptokeyModal.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/chatArea.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
The beginning of time. Nothing exists beyond this point.
9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |

Users

20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/chatInput.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 10 | 11 | <% if (encrypted) { %> 12 | 19 | <% } else { %> 20 | 27 | <% } %> 28 | 35 | 42 |
43 |
44 |
45 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/chatPanel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/fileUpload.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%- fileName %> 4 | <%- fileSize %> 5 |
6 | <% if (img) { %> 7 | 8 | <% } %> 9 |
10 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/linkedImage.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 | 12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/mediaLog.html: -------------------------------------------------------------------------------- 1 |
2 |

Media & Links

3 |
4 |
5 | 8 | 11 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 |

Automatically embed media (image links, website previews, youtube videos)?

24 |

This can reduce your anonymity.

25 |
26 |
27 | 28 |

Not for this channel...

29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | 40 | 44 |
45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/pgpPassphraseModal.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/userListUser.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | data-timestamp="<%- idleSince %>" 6 | <% } %> 7 | ><%- nick %> 8 | <% if (using_encryption) { %> 9 | 13 | 14 | 15 | <% } %> 16 | <% if (has_public_key) { %> 17 | 21 | 22 | 23 | <% } %> 24 | 25 | <% if (operator) { %> 26 | 27 | 28 | 29 | <% } %> 30 | <% if (inCall === true) { %> 31 | 32 | 33 | 34 | <% } %> 35 |
36 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/webshotBadge.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | INFO 5 |
6 |
7 | <%- title %>: 8 | <%- excerpt %> 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/client/modules/chat/templates/webshotMediaItem.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 | <%- excerpt %> 18 |
-------------------------------------------------------------------------------- /src/client/modules/chat/templates/youtube.html: -------------------------------------------------------------------------------- 1 |
2 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /src/client/modules/code/templates/jsCodeRepl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
HTML/CSS
4 |
5 | 6 |
7 |
8 |
9 |
JavaScript
10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 | 21 | 22 | 26 |
27 |
28 | 29 |
30 |
31 |

Result

32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/client/modules/draw/templates/drawing.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/client/modules/info/client.js.coffee: -------------------------------------------------------------------------------- 1 | versions = require("../../version.js.coffee") 2 | utility = require("../../utility.js.coffee") 3 | Mewl = require("../../ui/Mewl.js.coffee").MewlNotification 4 | 5 | module.exports.InfoClient = class InfoClient extends Backbone.View 6 | 7 | initialize: (opts) -> 8 | _.bindAll.apply(_, [this].concat(_.functions(this))) 9 | 10 | @config = opts.config 11 | @module = opts.module 12 | @socket = io.connect(@config.host + "/info") 13 | @channelName = opts.room 14 | 15 | #Initialize a path variable to hold the paths buffer as we recieve it from other clients 16 | @listen() 17 | @render() 18 | @attachEvents() 19 | 20 | listen: -> 21 | socket = @socket 22 | @socketEvents = 23 | private: => 24 | window.events.trigger("private:#{@channelName}") 25 | 26 | "info:latest_supported_client_version": (msg) => 27 | window.checkingVersion = false 28 | theirs = utility.versionStringToNumber(msg) 29 | ours = utility.versionStringToNumber(versions.CLIENT_VERSION) 30 | if theirs > ours 31 | growl = new Mewl 32 | title: "Client out of date" 33 | body: "The server does not support this client. Reloading..." 34 | 35 | setTimeout -> 36 | location.reload() 37 | , 5000 38 | 39 | _.each @socketEvents, (value, key) => 40 | 41 | # listen to a subset of event 42 | socket.on "#{key}:#{@channelName}", value 43 | 44 | # initialize the channel 45 | socket.emit "subscribe", room: @channelName, @postSubscribe 46 | 47 | #On successful reconnect, attempt to rejoin the room 48 | socket.on "reconnect", => 49 | 50 | #Resend the subscribe event 51 | socket.emit "subscribe", 52 | room: @channelName 53 | reconnect: true 54 | , @postSubscribe 55 | 56 | kill: -> 57 | 58 | attachEvents: -> 59 | 60 | postSubscribe: -> 61 | return if window.checkingVersion 62 | window.checkingVersion = true 63 | 64 | @socket.emit "info:latest_supported_client_version:#{@channelName}", "" 65 | 66 | refresh: -> 67 | 68 | render: -> 69 | -------------------------------------------------------------------------------- /src/client/options.js.coffee: -------------------------------------------------------------------------------- 1 | # consider these persistent options 2 | # we use a cookie for these since they're small and more compatible 3 | module.exports.Options = class Options 4 | 5 | defaults: 6 | show_mewl : true 7 | suppress_join : true 8 | highlight_mine : true 9 | prefer_24hr_clock : false 10 | show_OS_notifications : true 11 | suppress_identity_acknowledgements : true 12 | join_default_channel : true 13 | auto_scroll : true 14 | play_notification_sounds : true 15 | 16 | updateOption: (value, option) -> 17 | $option = $("#" + option) 18 | 19 | valueFromCookie = $.cookie(option) # check if the options are in the cookie, if so update the value 20 | value = JSON.parse(valueFromCookie) if valueFromCookie 21 | 22 | window.OPTIONS[option] = value 23 | if value 24 | $("body").addClass option 25 | $option.attr "checked", "checked" 26 | else 27 | $("body").removeClass option 28 | $option.removeAttr "checked" 29 | 30 | # bind events to the click of the element of the same ID as the option's key 31 | $option.on "click", -> 32 | $.cookie option, $(this).prop("checked"), window.COOKIE_OPTIONS 33 | OPTIONS[option] = not OPTIONS[option] 34 | if OPTIONS[option] 35 | $("body").addClass option 36 | else 37 | $("body").removeClass option 38 | 39 | updateAllOptions: () -> 40 | _.each window.OPTIONS, @updateOption 41 | 42 | constructor: (@options) -> 43 | if !@options 44 | @options = @defaults 45 | 46 | window.OPTIONS = @options 47 | @updateAllOptions() 48 | -------------------------------------------------------------------------------- /src/client/regex.js.coffee: -------------------------------------------------------------------------------- 1 | # utility: a container of useful regexes arranged into some a rough taxonomy 2 | module.exports.REGEXES = 3 | urls: 4 | image: /(\b(https?|http):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|].(jpeg|jpg|png|bmp|gif|svg))([\.#?a-zA-Z0-9&=-_~])*/gi 5 | youtube: /((https?:\/\/)?(www\.)?youtu((?=\.)\.be\/|be\.com\/watch.*v=)([\w\d\-_]*))/gi 6 | all_others: /(\b(https?|http):(\/\/|//)[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|;])/gi 7 | 8 | users: 9 | mentions: /((^@[^\b\s]*)|((?:\s+)@[^\b\s]*))/gi 10 | 11 | commands: 12 | nick: /^\/(nick|n) / 13 | topic: /^\/(topic) / 14 | broadcast: /^\/(broadcast|bc) / 15 | failed_command: /^\// 16 | private: /^\/private/ 17 | public: /^\/public/ 18 | help: /^\/help/ 19 | password: /^\/(password|pw)/ 20 | private_message: /^\/(pm|w|whisper|t|tell|msg) / 21 | join: /^\/(join|j)/ 22 | leave: /^\/leave/ 23 | pull_logs: /^\/(pull|p|sync|s) / 24 | set_color: /^\/(color|c) / 25 | edit: /^(\/edit) #?(\d*) / 26 | chown: /^\/chown / 27 | chmod: /^\/chmod / 28 | reply: /(>>|>>)(\d+)/g 29 | github: /^\/github / 30 | roll: /^\/(roll|r)( |)/ 31 | destroy: /^\/destroy/ 32 | pseudonym: /^\/(pseudonym|nym)/ 33 | 34 | github_subcommands: 35 | track: /^track/ 36 | 37 | colors: 38 | hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i # matches 3 and 6-char hex colour codes, optional # 39 | -------------------------------------------------------------------------------- /src/client/templates/MewlNotification.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

<%- title %>

4 |

<%- body %>

5 | <% if (closable) { %> 6 |
7 | 8 |
9 | <% } %> 10 |
11 | -------------------------------------------------------------------------------- /src/client/templates/channelSelector.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% _.each(channels, function (name) { %> 4 | 19 | <% }); %> 20 |
21 |
22 | -------------------------------------------------------------------------------- /src/client/templates/channelSelectorButton.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | / 12 | 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /src/client/ui/Faviconizer.js.coffee: -------------------------------------------------------------------------------- 1 | module.exports.Faviconizer = class Faviconizer 2 | setIcon: (dataURI) -> 3 | $favicon = $("link[rel~='icon']") 4 | $clone = $favicon.clone() 5 | $clone.attr "href", dataURI 6 | $favicon.remove() 7 | $("head").append $clone 8 | 9 | activity: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90HGQIjG8oNC7cAAAK0SURBVDjLpVNLTxNRGD137kzfpYCV1ghqCYZgdMAQExIjmpgMxqiJRI0bMJE/5cIVLJRojAuNTGOMBIwbX9TE1lSKtircPmgL7TC387hulIjiyvPlLL4v5zurcyCEwG60XVt1XOed7drqvzRCCJCmaeJPCImrAkIXwo0TIjECohHXm8IuIGvr6zsOsp+rlmPqtrDjQrggRIJMZKZQn2Zv/W1CMoXC9hJsN1XD2tBNx4gL4WL+630AwJmea/DRAAsobVqz5tthQl6m0wCAjlhTrfGSbtrNuCscLJUXMNx1FkIIvCk9w/G9Z+CTg6zdu1ersuC2iVyu1xE/tKku1/K6YW/GbddCfjON091XEQv0gECCV/Zj/us99EbUWGXre7Kr44C29jmcAgDyLD+rfmt80uutctxyLVRNhrGDE9gXTEChPgCA5ZhYba5A/zKDrkA3Ip4o2x/q075kOlJ09OYRPd/I9FXNIvJrOZxKXEQicgwK9UL8HIlQeOUAFOrBYvYJXIWHHNc5Wf4WuSV5qHeS2yarrJdhVFvgzhYcYUMIdwdd4aDlmOANG9w2mVf23yjVaqCRxHk2MOBJlozv442SGVqzc+gMRxFW2qFQDwABw9pAtvYWT5dnofgpOxwdPLcw1/bO4By0e2QE2bTCho57k8VKcXz1fS20io8Ih9sQ9nSi5XB8rL7C3Ic72GAmG0gc1Z4+7FgyOIfBOWjn0BAMzpFOedjwaZIsFarjy4vlUFlahuEvIld/j+evdZSyDTZwold7fCea+vVscA7SPzW1I1kXJgvq6wcrydUPG7FobxBUkSBRwobHE9qj6Z6/kkjl/n6YlrXN1Cs/G70ikpWV5mWW2Qx5AjIbvLxfm70dS/2u+0USuHRpt47g+kRRzb2oTPee3DN5d6Zr1yIBAMHYGP4HPwAVUJhfj1XVlgAAAABJRU5ErkJggg==" 10 | connected: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAChUlEQVQ4T5XTX0hTURwH8O/ZzZm6wsXmvZgDlTIXepdpaEoSJRc10AiUQrSXEKwHIbIHi8SHjLCXfNCHetGXIAyyP8gdGlEIisbW1CylMdS0o6uVf6b7d09jMmE4o+7jub/zOb/z+50fwS4fY1QEtD2Aq44Q3rZbHIn2Y80zJyKglQlUAoNCwbkkTawhKrIDoD/tIhSNzAJMAGMACYZwhBLVmsQfSN+BRAB2+ln0utUyfH4BioKJ4a5QglmF10BiOBoT75PS+cwIZBsYt38Q3WucrGx6BQQCcM5cx/G8qVASlrFM6IwdIHvVNEETkLLTc7eREDA8/k50uhSZrW8I8PvB6G0UnZ5AohAIXgH4tajC0NsskIP3QTRxVJeokgqyi0MI6R/uF5eWvbLye1WAz4f49Xs4WzIDbTIDUW+VmHmDvVggGBw4DHfiHXD791F9kloqKyizkY6+XuvmPDXB7QZ+vER1+RAMYmB7c7hLzAPMT3J4+qoQzHARsSlJtsbKKhN51N8t2qddZv/SMp+wIqO2woLUY0EgLrLBbAOY/ahCz+tcbKbW0LRD+tL6czXWUA3aezvFL5Pfzf4Fyp/UPUOl5IT+SPB+8VuIEkzOOQ30DegxyjXQDGNK6c3qemuoBuFzmrvbxSnLnNk3v8CXp5lxvmQFSUe3IpY/Ac8HtTB7aqkxxyC1XW6K7EIYaexsES2jDrPXMctXGUdxxrQeAt5MaNC3eoHmnEiVHl5tjf4OwsiV9mZxZGTK7P3q4PN0DrA9HMYTTtH8fKP0uKnt7y8xjFy6e0Mcem+TPd8WhdhkgRYVm6Qntx782yyEkYqWBnFs2tmTl6Gre9Ha9X/TuNvoRlv/A6R//RELL8+9AAAAAElFTkSuQmCC" 11 | disconnected: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90HGQEjAqwgHS4AAAJYSURBVDjLpVMxT9tAFP7OseXYjoQogw1UGaJK3bxDhcSCqUTFkoUudOBPdWZhajNUjWQGFpBSJpAXKipFCZSEi5OALXyxdfZdN9Q0YeonveGe3n1673vfg5QS84Ix5hZFcckYc1+qkVKCJGmKf5Fnmatpmg/AAUA5556q6wHmgNyPx1MJJc9dQogvhHCklCCEQFEUKqX0hKrOkJCft7fPD1NKl3Pu53nuSCnRbDYBADs7O1BVlWqa5jFCpkhI6+oKAPBKUdwkSXzOuSOEQKPRgKZpkFIiz3PU63VomkYty/LGQjyTkG8/fuC1rruj0cjPsswRQuDk5AS6rkPXdQBAlmVI0xRbW1vQdZ0uLS15v7MsAABydnbm9vt9fzKZOEVR4Pz8HAsLCyiXy1OzpmmKKIqwvr4OwzDo8vKy94uxoLS5uekPBoM3cRzj4uIClUoFlmXNqK2qKqSUuL6+hmEYlTRN3w2K4rNSLpf3GWN0NBqBMQYhBF6ClBKTyQSMMWoYxqfw8RFK8+YmsG3b45xT0zTR7/eRJMnMxyRJ0Ov14DgOdRzn/fdu9zKMIijDOEaj2w2q1aonhKBZlqHdbuPp6elvV6LT6cCyLFqtVr2vnc7lMI4xjGOowygCABxFUfChVvPG4/FxGIY25xyrq6sAgLu7O5RKJVqr1byjdnvaB28PDqbardu222q1jsMwtE3TBCEEpmnStbU17wuls0609/ZmxProOO7p6an/8PDgLC4u0o2NDe/o/n7+LZi7u3MV319ZcYMgOHRdd/+w1wte2gzB9jb+B38ALsRj19PJcOEAAAAASUVORK5CYII=" 12 | 13 | setActivity: -> 14 | return if @state == 'activity' 15 | @setIcon @activity 16 | @state = 'activity' 17 | 18 | setConnected: -> 19 | return if @state == 'connected' 20 | @setIcon @connected 21 | @state = 'connected' 22 | 23 | setDisconnected: -> 24 | return if @state == 'disconnected' 25 | @setIcon @disconnected 26 | @state = 'disconnected' 27 | -------------------------------------------------------------------------------- /src/client/ui/Mewl.js.coffee: -------------------------------------------------------------------------------- 1 | mewlTemplate = require("../templates/MewlNotification.html") 2 | 3 | # Displays a little modal-like alert box with 4 | module.exports.MewlNotification = class MewlNotification extends Backbone.View 5 | 6 | template: mewlTemplate 7 | className: "growl" 8 | 9 | events: 10 | "click .j-close": "hide" 11 | 12 | initialize: (opts) -> 13 | _.bindAll.apply(_, [this].concat(_.functions(this))) 14 | 15 | # defaults 16 | @closable = true 17 | @classes = "notif-white" 18 | @position = "bottom right" 19 | @padding = 10 20 | @lifespan = opts.lifespan || 3000 21 | 22 | @set(opts) 23 | @$el.addClass @position 24 | @place().show() 25 | 26 | render: -> 27 | @$el.html @template( 28 | classes: @classes 29 | title: @title 30 | body: @body 31 | closable: @closable 32 | ) 33 | 34 | set: (opts) -> 35 | _.extend this, opts 36 | @render() 37 | 38 | show: -> 39 | self = this 40 | $("body").append @$el 41 | _.defer => @$el.addClass "shown" 42 | 43 | setTimeout @hide, @lifespan if @lifespan != Infinity 44 | 45 | return @ # for chaining 46 | 47 | hide: -> 48 | @$el.on 'webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd', => @$el.remove() 49 | @$el.addClass "slide-right" 50 | 51 | return @ # for chaining 52 | 53 | place: -> 54 | $otherGrowls = $(".growl:visible." + @position.replace(" ", ".")) # finds all with the same position settings as ours 55 | 56 | cssString = if @position.indexOf("bottom") != -1 57 | "bottom" 58 | else 59 | "top" 60 | 61 | max = -Infinity 62 | heightOfMax = 0 63 | 64 | # find the offset of the highest visible growl 65 | # and place ourself above it 66 | i = 0 67 | 68 | while i < $otherGrowls.length 69 | $otherEl = $($otherGrowls[i]) 70 | curValue = parseInt($otherEl.css(cssString), 10) 71 | if curValue > max 72 | max = curValue 73 | heightOfMax = $otherEl.outerHeight() 74 | i++ 75 | max += heightOfMax 76 | max += @padding # some padding 77 | @$el.css cssString, max 78 | 79 | return @ # for chaining 80 | -------------------------------------------------------------------------------- /src/client/ui/Notifications.js.coffee: -------------------------------------------------------------------------------- 1 | # object: a wrapper around Chrome OS-level notifications 2 | module.exports.Notifications = class Notifications 3 | 4 | _permission = "default" 5 | _growl = null 6 | 7 | defaults: 8 | title: "echoplexus" 9 | dir: "auto" 10 | icon: window.location.origin + "/echoplexus-logo.png" 11 | iconUrl: window.location.origin + "/echoplexus-logo.png" 12 | lang: "" 13 | body: "" 14 | tag: "" 15 | TTL: 5000 16 | onshow: -> 17 | onclose: -> 18 | onerror: -> 19 | onclick: -> 20 | 21 | constructor: -> 22 | _permission = window.Notification?.permission 23 | 24 | if window.ua?.node_webkit? 25 | _permission = "granted" 26 | _growl = window.requireNode("growl") 27 | 28 | # 29 | # Polyfill to present an OS-level notification: 30 | # options: { 31 | # title: "Displays at the top", 32 | # dir: "auto", // text direction 33 | # lang: "", 34 | # body: "The text you want to display", 35 | # tag: "the class of notifications it's in", 36 | # TTL: (milliseconds) amount of time to keep it alive 37 | # } 38 | # 39 | notify: (userOptions, focusOverride = false) -> 40 | if !focusOverride 41 | if window.visibility_status == "visible" 42 | console.log "Document is focused, so notifications are suppressed" 43 | return 44 | if !OPTIONS["show_OS_notifications"] 45 | console.log "Suppressing client notification due to client preference" 46 | return 47 | if _permission != "granted" 48 | console.log "Unable to display notification: user has not granted permission to do so" 49 | return 50 | 51 | console.log 'Attempting to notify' 52 | 53 | if OPTIONS["play_notification_sounds"] 54 | document.getElementById("notification-sound").play() 55 | 56 | opts = _.clone(@defaults) 57 | _.extend opts, userOptions 58 | title = opts.title || "" 59 | delete opts.title 60 | 61 | if window.ua.node_webkit # Application 62 | if process.platform is "linux" 63 | _growl opts.body, 64 | image: process.cwd() + "/echoplexus-logo.png" 65 | else if window.Notification # Standards 66 | notification = new Notification(title, opts) 67 | setTimeout (-> 68 | notification.close() 69 | ), opts.TTL 70 | # else # screw the other webkitNotifications mozNotifications and others 71 | 72 | # 73 | # (Boolean) Are OS notification permissions granted? 74 | # 75 | hasPermission: -> 76 | _permission 77 | 78 | # 79 | # Polyfill to request notification permission 80 | # 81 | requestNotificationPermission: -> 82 | if _permission is "default" # only request it if we don't have it 83 | if window.Notification 84 | window.Notification.requestPermission (perm) -> 85 | _permission = perm 86 | 87 | request: @requestNotificationPermission 88 | -------------------------------------------------------------------------------- /src/client/utility.js.coffee: -------------------------------------------------------------------------------- 1 | # 2 | #utility: 3 | # useful extensions to global objects, if they must be made, should be made here 4 | # 5 | 6 | module.exports.isMobile = -> 7 | return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(navigator.userAgent) 8 | 9 | module.exports.HTMLSanitizer = class HTMLSanitizer 10 | 11 | sanitize: (htmlString, allowedElements, allowedAttributes) -> 12 | 13 | ALLOWED_TAGS = ["STRONG", "EM", "P", "A", "UL", "LI"] 14 | ALLOWED_ATTRIBUTES = ["href", "title"] 15 | 16 | ALLOWED_TAGS = allowedElements if allowedElements 17 | ALLOWED_ATTRIBUTES = allowedAttributes if allowedAttributes 18 | 19 | clean = (el) -> 20 | tags = Array.prototype.slice.apply(el.getElementsByTagName("*"), [0]) 21 | for tag, i in tags 22 | # throw it out, and all of its children, if it's not allowed 23 | if ALLOWED_TAGS.indexOf(tag.nodeName) == -1 24 | usurp(tags[i]) 25 | # now remove all the troublesome attributes 26 | attrs = tag.attributes 27 | for attribute in tag.attributes 28 | if ALLOWED_ATTRIBUTES.indexOf(attribute.name) == -1 or attribute.value.match(/javascript:/gi) 29 | delete tag.attributes.removeNamedItem(attribute.name) 30 | 31 | null 32 | 33 | usurp = (p) -> 34 | p.parentNode.removeChild(p) 35 | null 36 | 37 | div = document.createElement("div"); 38 | div.innerHTML = htmlString 39 | clean(div) 40 | return div.innerHTML 41 | 42 | module.exports.versionStringToNumber = (versionString) -> 43 | numeric = versionString.replace(/\./g,'').replace(/r/gi,'').split('') 44 | 45 | sum = 0 46 | 47 | i = numeric.length - 1 48 | j = 0 49 | while i >= 0 # fuckin coffeescript loops 50 | sum += parseInt(numeric[i], 10) * Math.pow(10,j) 51 | j++ 52 | i-- 53 | 54 | sum 55 | 56 | window.splitFingerprint = (s) -> 57 | s.substring(0,20) + "\n" + s.substring(20,40) -------------------------------------------------------------------------------- /src/client/version.js.coffee: -------------------------------------------------------------------------------- 1 | module.exports.CLIENT_VERSION = '0.2.6'; 2 | -------------------------------------------------------------------------------- /src/client/visibility.js.coffee: -------------------------------------------------------------------------------- 1 | # polyfill from http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active 2 | # automatically exposes the property window.visibility_status = "hidden" || "visible" 3 | (-> 4 | hiddenProperty = "hidden" 5 | 6 | create_onchange = (fn) -> 7 | 8 | return (evt) -> 9 | v = 'visible' 10 | h = 'hidden' 11 | evtMap = 12 | focus: v 13 | focusin: v 14 | pageshow: v 15 | blur: h 16 | focusout: h 17 | pagehide: h 18 | 19 | evt = evt || window.event; 20 | if (evt.type in evtMap) 21 | # console.log "Visibility predicted to be #{evtMap[evt.type]}" 22 | fn(evt.type) 23 | else 24 | visibility = if document[hiddenProperty] then "hidden" else "visible" 25 | # console.log "Visibility known to be #{visibility}" 26 | fn(visibility) 27 | 28 | # polyfilling an attaching handler 29 | attachOnchange = (onchange) -> 30 | if (typeof document.hidden != 'undefined') 31 | hiddenProperty = 'hidden' 32 | document.addEventListener("visibilitychange", onchange) 33 | else if (typeof document.mozHidden != 'undefined') 34 | hiddenProperty = 'mozHidden' 35 | document.addEventListener("mozvisibilitychange", onchange) 36 | else if (typeof document.webkitHidden != 'undefined') 37 | hiddenProperty = 'webkitHidden' 38 | document.addEventListener("webkitvisibilitychange", onchange) 39 | else if (typeof document.msHidden != 'undefined') 40 | hiddenProperty = 'msHidden' 41 | document.addEventListener("msvisibilitychange", onchange) 42 | else if ('onfocusin' in document) # IE 9 and lower: 43 | document.onfocusin = document.onfocusout = onchange; 44 | else 45 | window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange; 46 | 47 | window.VisibilityManager = 48 | onChange: (fn) -> 49 | onchangeHandler = create_onchange(fn) 50 | attachOnchange(onchangeHandler) 51 | 52 | )() 53 | -------------------------------------------------------------------------------- /src/embedded-client/main.js.coffee: -------------------------------------------------------------------------------- 1 | require("../client/bootstrap.core.js.coffee").core() 2 | Client = require('../client/client.js.coffee') 3 | ClientModel = Client.ClientModel 4 | ClientsCollection = Client.ClientsCollection 5 | ChatClient = require('../client/modules/chat/client.js.coffee').ChatClient 6 | 7 | # overwrite with noops since these modes don't technically exist in embedded mode: 8 | window.codingModeActive = window.chatModeActive = window.showPrivateOverlay = window.hidePrivateOverlay = -> 9 | 10 | $(document).ready -> 11 | 12 | # grab the query params from the iframe URL: 13 | params = window.location.search.substr(1) # trim off leading ? 14 | params = params.split("&") 15 | options = {} 16 | _.map params, (ele) -> 17 | [key, val] = ele.split("=") 18 | options[key] = val 19 | 20 | channelName = options.channel || "/" 21 | 22 | # connect to socket 23 | io.connect window.location.origin, 24 | "connect timeout": 1000 25 | reconnect: true 26 | "reconnection delay": 2000 27 | "max reconnection attempts": 1000 28 | 29 | # and throw together a single channel's view 30 | channel = new Backbone.Model 31 | clients: new ClientsCollection() 32 | modules: [] 33 | authenticated: false 34 | isPrivate: false 35 | 36 | singleChat = new ChatClient 37 | channel: channel 38 | room: channelName 39 | config: 40 | host: window.SOCKET_HOST 41 | 42 | singleChat.trigger "show" 43 | 44 | $("body").append(singleChat.$el) 45 | -------------------------------------------------------------------------------- /src/mobile/ui/gestures.js.coffee: -------------------------------------------------------------------------------- 1 | module.exports.TouchGestures = class TouchGestures extends Backbone.Model 2 | 3 | initialize: -> 4 | console.log 'initializing touch gestures' 5 | _.bindAll.apply(_, [this].concat(_.functions(this))) 6 | @set 'switcherInactive', false 7 | @windowEl = $(window)[0] 8 | @$switcher = $("nav.functionality") 9 | @switcherEl = @$switcher[0] 10 | @attachEvents() 11 | @gestureTolerance = 100 # ms 12 | 13 | attachEvents: -> 14 | 15 | Hammer(@windowEl).on "dragright", (ev) => 16 | @dragrightStart = new Date() if !@dragrightStart 17 | if ev.gesture.center.pageX > 100 18 | @openSwitcher(ev, true) 19 | 20 | Hammer(@windowEl).on "dragleft", (ev) => 21 | @dragleftStart = new Date() if !@dragleftStart 22 | if !@get("switcherInactive") 23 | @closeSwitcher(ev, true) 24 | 25 | $("#channel-switcher").on "click", ".channel-switcher-contents button", => 26 | if !@get("switcherInactive") 27 | @$switcher.addClass("inactive") 28 | @set "switcherInactive", true 29 | 30 | Hammer(@windowEl).on "release", @fingerUp 31 | 32 | openSwitcher: (ev, acquireLock) -> 33 | return if !@get('switcherInactive') 34 | @gestureLock = "openSwitcher" if !@gestureLock and acquireLock 35 | return if @gestureLock != "openSwitcher" 36 | 37 | now = new Date() 38 | if now - @dragrightStart > @gestureTolerance 39 | @set 'switcherInactive', false 40 | @$switcher.removeClass("inactive") 41 | delete @dragrightStart 42 | 43 | ev.gesture.preventDefault() 44 | ev.preventDefault() 45 | 46 | closeSwitcher: (ev, acquireLock) -> 47 | return if @get('switcherInactive') 48 | @gestureLock = "closeSwitcher" if !@gestureLock and acquireLock 49 | return if @gestureLock != "closeSwitcher" 50 | 51 | now = new Date() 52 | if now - @dragleftStart > @gestureTolerance 53 | @set 'switcherInactive', true 54 | @$switcher.addClass("inactive") 55 | delete @dragleftStart 56 | 57 | ev.gesture.preventDefault() 58 | ev.preventDefault() 59 | 60 | fingerUp: (ev) -> 61 | delete @dragleftStart 62 | delete @dragrightStart 63 | @touchesFrozen = false 64 | @gestureLock = "" 65 | -------------------------------------------------------------------------------- /src/server/CodeServer.coffee: -------------------------------------------------------------------------------- 1 | config = require('./config.coffee').Configuration 2 | ApplicationError = require("./Error.js.coffee") 3 | AbstractServer = require('./AbstractServer.coffee').AbstractServer 4 | Client = require('../client/client.js.coffee').ClientModel 5 | Clients = require('../client/client.js.coffee').ClientsCollection 6 | DEBUG = config.DEBUG 7 | 8 | 9 | module.exports.CodeCache = class CodeCache 10 | 11 | constructor: (namespace) -> 12 | @currentState = "" 13 | @namespace = "" 14 | @namespace = namespace if namespace? 15 | @mruClient = undefined 16 | @ops = [] 17 | 18 | set: (state) -> 19 | @currentState = state 20 | @ops = [] 21 | 22 | add: (op, client) -> 23 | @mruClient = client 24 | @ops.push op 25 | 26 | syncFromClient: -> 27 | return if !mruClient? 28 | @mruClient.socketRef.emit "code:request:#{@namespace}" 29 | 30 | syncToClient: -> 31 | start: @currentState 32 | ops: @ops 33 | 34 | remove: (client) -> 35 | @mruClient = null if mruClient is client 36 | 37 | 38 | module.exports.CodeServer = class CodeServer extends AbstractServer 39 | 40 | name: "CodeServer" 41 | namespace: "/code" 42 | constructor: -> 43 | @codeCaches = {} 44 | super 45 | 46 | subscribeError: (err, socket, channel, client) -> 47 | if err and not err instanceof ApplicationError.AuthenticationError 48 | console.log("CodeServer: ", err) 49 | subscribeSuccess: (effectiveRoom, socket, channel, client) -> 50 | cc = @spawnCodeCache(effectiveRoom) 51 | socket.in(effectiveRoom).emit("code:authoritative_push:#{effectiveRoom}", cc.syncToClient()); 52 | 53 | events: 54 | "code:cursorActivity": (namespace, socket, channel, client, data) -> 55 | socket.in(namespace).broadcast.emit "code:cursorActivity:#{namespace}", 56 | cursor: data.cursor, 57 | id: client.get("id") 58 | 59 | "code:change": (namespace, socket, channel, client, data) -> 60 | codeCache = @spawnCodeCache namespace 61 | 62 | data.timestamp = Number(new Date()) 63 | codeCache.add data, client 64 | socket.in(namespace).broadcast.emit "code:change:#{namespace}", data 65 | 66 | "code:full_transcript": (namespace, socket, channel, client, data) -> 67 | codeCache = @spawnCodeCache namespace 68 | 69 | codeCache.set data.code 70 | socket.in(namespace).broadcast.emit "code:sync:#{namespace}", data 71 | 72 | spawnCodeCache: (ns) -> 73 | if @codeCaches[ns]? 74 | #DEBUG and console.log("note: Aborted spawning a code that already exists", ns) 75 | return @codeCaches[ns] 76 | 77 | cc = new CodeCache(ns) 78 | @codeCaches[ns] = cc 79 | setInterval cc.syncFromClient, 1000 * 30 # need something more elegant than this.. 80 | cc 81 | -------------------------------------------------------------------------------- /src/server/DrawServer.coffee: -------------------------------------------------------------------------------- 1 | _ = require('underscore') 2 | ApplicationError = require("./Error.js.coffee") 3 | AbstractServer = require('./AbstractServer.coffee').AbstractServer 4 | Client = require('../client/client.js').ClientModel 5 | Clients = require('../client/client.js').ClientsCollection 6 | config = require('./config.coffee').Configuration 7 | DEBUG = config.DEBUG 8 | 9 | module.exports.DrawServer = class DrawingServer extends AbstractServer 10 | 11 | name: "DrawServer" 12 | namespace: "/draw" 13 | 14 | subscribeError: (err, socket, channel, client) -> 15 | if err and err instanceof ApplicationError.AuthenticationError 16 | console.log("DrawServer: ", err) 17 | subscribeSuccess: (effectiveRoom, socket, channel, client) -> 18 | room = channel.get("name") 19 | 20 | # play back what has happened 21 | socket.emit("draw:replay:#{room}", channel.replay) 22 | 23 | events: 24 | "draw:line": (namespace, socket, channel, client, data) -> 25 | room = channel.get("name") 26 | 27 | channel.replay.push(data) 28 | 29 | socket.in(room).broadcast.emit "draw:line:#{room}", (_.extend data, id: client.get("id")) 30 | 31 | "trash": (namespace, socket, channel, client, data) -> 32 | room = channel.get("name") 33 | 34 | channel.replay = [] 35 | socket.in(room).broadcast.emit "trash:#{room}", data 36 | -------------------------------------------------------------------------------- /src/server/Error.js.coffee: -------------------------------------------------------------------------------- 1 | # http://dustinsenos.com/articles/customErrorsInNode 2 | util = require("util") 3 | 4 | module.exports.AbstractError = class AbstractError extends Error 5 | 6 | name: "Abstract Error" 7 | constructor: (msg, constr) -> 8 | # If defined, pass the constr property to V8's 9 | # captureStackTrace to clean up the output 10 | Error.captureStackTrace this, constr or this 11 | 12 | # If defined, store a custom error message 13 | @message = msg or "Error" 14 | 15 | module.exports.AuthenticationError = class AuthenticationError extends module.exports.AbstractError 16 | 17 | name: "Authentication Failure" 18 | constructor: (msg, constr) -> 19 | super 20 | -------------------------------------------------------------------------------- /src/server/EventBus.coffee: -------------------------------------------------------------------------------- 1 | GLOBAL_EVENTBUS = undefined 2 | 3 | module.exports.EventBus = -> 4 | GLOBAL_EVENTBUS = new Backbone.Model if !GLOBAL_EVENTBUS 5 | 6 | return GLOBAL_EVENTBUS 7 | -------------------------------------------------------------------------------- /src/server/GithubWebhookIntegration.coffee: -------------------------------------------------------------------------------- 1 | config = require("./config.coffee").Configuration # deploy specific configuration 2 | redisC = require("./RedisClient.coffee").RedisClient(config.redis?.port, config.redis?.host) 3 | crypto = require('crypto') 4 | utility = new (require("./utility.coffee").Utility) 5 | async = require("async") 6 | DEBUG = config.DEBUG 7 | 8 | # hackish, as anybody could really end up spoofing this information 9 | # so, we don't let them do too much with this capability 10 | module.exports.allowRepository = (room, repo_url, callback) -> 11 | redisC.hget "github:webhooks", repo_url, (err, rooms) -> 12 | throw err if err 13 | 14 | if rooms 15 | rooms = JSON.parse(rooms) 16 | else 17 | rooms = [] 18 | rooms.push room 19 | 20 | utility.getSimpleSaltedMD5 room, (err, hash) -> 21 | throw err if err 22 | 23 | async.parallel { 24 | token: (callback) -> 25 | redisC.hset "github:webhooks:tokens", hash, room, callback 26 | webhook: (callback) -> 27 | redisC.hset "github:webhooks", repo_url, JSON.stringify(rooms), callback 28 | }, (err, stored) -> 29 | DEBUG && console.log(err, hash, stored) 30 | throw err if err 31 | callback?(null, hash) 32 | 33 | module.exports.verifyAllowedRepository = (token, callback) -> 34 | redisC.hget "github:webhooks:tokens", token, (err, reply) -> 35 | throw err if err 36 | 37 | if reply 38 | callback?(null, reply) 39 | else 40 | callback?("Ignoring GitHub postreceive hook's request: token not found!") 41 | 42 | module.exports.prettyPrint = (githubResponse) -> 43 | r = githubResponse 44 | 45 | pluralize = (noun, n) -> 46 | if n > 1 47 | noun + "s" 48 | else 49 | noun 50 | 51 | details = for c in r.commits 52 | "
  • #{c.message}
  • " 53 | 54 | details = details.join("") # override default join which uses ',' 55 | 56 | branchName = r.ref.replace("refs/heads/", "") 57 | branchURL = "#{r.repository.url}/tree/#{branchName}" 58 | 59 | " 60 | #{r.pusher.name} just pushed #{r.commits.length} #{pluralize('commit', r.commits.length)} to 61 | #{r.repository.name} (#{branchName}) 62 |
      63 | #{details} 64 |
    " 65 | 66 | module.exports.gravatarURLHash = (emailAddress) -> 67 | emailAddress = emailAddress.trim() 68 | emailAddress = emailAddress.toLowerCase() 69 | 70 | md5 = crypto.createHash 'md5' 71 | md5.update emailAddress 72 | md5.digest('hex') 73 | 74 | module.exports.gravatarURL = (emailAddress) -> 75 | "https://www.gravatar.com/avatar/#{module.exports.gravatarURLHash(emailAddress)}.jpg?s=16&d=identicon" 76 | -------------------------------------------------------------------------------- /src/server/InfoServer.coffee: -------------------------------------------------------------------------------- 1 | ApplicationError = require("./Error.js.coffee") 2 | versions = require('./version.coffee') 3 | _ = require('underscore') 4 | AbstractServer = require('./AbstractServer.coffee').AbstractServer 5 | Client = require('../client/client.js').ClientModel 6 | Clients = require('../client/client.js').ClientsCollection 7 | config = require('./config.coffee').Configuration 8 | DEBUG = config.DEBUG 9 | 10 | # this server is meant to expose all manners of metadata about the host 11 | # that is operating echoplexus 12 | # for instance, a client could query capabilities or client versions supported 13 | module.exports.InfoServer = class InfoServer extends AbstractServer 14 | 15 | name: "InfoServer" 16 | namespace: "/info" 17 | 18 | subscribeSuccess: (effectiveRoom, socket, channel, client) -> 19 | 20 | subscribeError: (err, socket, channel, client) -> 21 | room = channel.get("name") 22 | 23 | if err and err instanceof ApplicationError.AuthenticationError 24 | console.log("InfoServer: ", err) 25 | socket.in(room).emit("private:#{room}") 26 | else 27 | socket.in(room).emit("chat:#{room}", @serverSentMessage({ 28 | body: err.message 29 | }, room)) 30 | 31 | events: 32 | "info:latest_supported_client_version": (namespace, socket, channel, client, data) -> 33 | room = channel.get("name") 34 | 35 | socket.in(room).emit "info:latest_supported_client_version:#{room}", versions.LATEST_SUPPORTED_CLIENT_VERSION 36 | -------------------------------------------------------------------------------- /src/server/PermissionModel.coffee: -------------------------------------------------------------------------------- 1 | _ = require("underscore") 2 | PermissionModel = require("../client/PermissionModel.coffee").PermissionModel 3 | 4 | module.exports.ClientPermissionModel = class ClientPermissionModel extends PermissionModel 5 | initialize: -> 6 | _.bindAll.apply(_, [this].concat(_.functions(this))) 7 | super 8 | 9 | upgradeToOperator: -> 10 | @set 11 | canSetTopic: true 12 | canMakePrivate: true 13 | canMakePublic: true 14 | canKick: true 15 | canMute: true 16 | canBan: true 17 | canSpeak: true 18 | canPullLogs: true 19 | canUploadFile: true 20 | canDeleteLogs: true 21 | canSetGithubPostReceiveHooks: true 22 | 23 | @canBestow = @attributes 24 | 25 | module.exports.ChannelPermissionModel = class ChannelPermissionModel extends PermissionModel 26 | 27 | defaults: 28 | canSetTopic: null # null represents no particular privilege or inhibition 29 | canMakePrivate: null 30 | canMakePublic: null 31 | canKick: null 32 | canMute: null 33 | canBan: null 34 | canSpeak: true 35 | canPullLogs: true 36 | canUploadFile: false 37 | canDeleteLogs: false 38 | canSetGithubPostReceiveHooks: null 39 | -------------------------------------------------------------------------------- /src/server/PhantomJS-Screenshot.js.coffee: -------------------------------------------------------------------------------- 1 | # usage: phantomjs this_file url file.out [width height] 2 | page = require("webpage").create() 3 | system = require("system") 4 | w = 1024 5 | h = 768 6 | address = undefined 7 | output = undefined 8 | size = undefined 9 | address = system.args[1] 10 | output = system.args[2] 11 | page.viewportSize = 12 | width: w 13 | height: h 14 | 15 | page.clipRect = 16 | width: w 17 | height: h 18 | 19 | if system.args.length is 5 # if a resolution was supplied 20 | w = system.args[3] 21 | h = system.args[4] 22 | page.viewportSize = 23 | width: w 24 | height: h 25 | 26 | page.clipRect = 27 | top: 0 28 | left: 0 29 | width: w 30 | height: h 31 | 32 | try 33 | page.open address, (status) -> 34 | if status isnt "success" 35 | console.log "Unable to load the address!" 36 | else 37 | window.setTimeout (-> # have to give phantom time to start up 38 | extracted_information = title: page.title 39 | 40 | # extract some data from the page: 41 | data = page.evaluate(-> 42 | meta = document.querySelector("meta[name='description']")?.getAttribute("content") || null 43 | firstParagraph = document.querySelector("p")?.textContent || null 44 | 45 | pageExcerpt = meta || firstParagraph || "" 46 | 47 | return { 48 | excerpt: pageExcerpt 49 | } 50 | ) 51 | if data.excerpt 52 | data.excerpt = data.excerpt.trim().replace(/\n/g, "") 53 | data.excerpt = data.excerpt.substring(0, 1024) + "..." if data.excerpt.length > 1024 54 | extracted_information.excerpt = data.excerpt 55 | console.log JSON.stringify(extracted_information) 56 | page.render output 57 | phantom.exit() 58 | ), 200 59 | catch e 60 | console.log "Screenshotter died mysteriously. #{e}" 61 | 62 | -------------------------------------------------------------------------------- /src/server/RedisClient.coffee: -------------------------------------------------------------------------------- 1 | redis = require("redis") 2 | GLOBAL_REDIS_CLIENT = undefined 3 | 4 | module.exports.RedisClient = (port, host, select) -> 5 | if !GLOBAL_REDIS_CLIENT 6 | if host? and port? 7 | GLOBAL_REDIS_CLIENT = redis.createClient(port, host) if !GLOBAL_REDIS_CLIENT 8 | meta = " on #{host}:#{port}" 9 | else 10 | GLOBAL_REDIS_CLIENT = redis.createClient() if !GLOBAL_REDIS_CLIENT 11 | 12 | GLOBAL_REDIS_CLIENT.once "ready", -> 13 | version = GLOBAL_REDIS_CLIENT.server_info.redis_version 14 | console.log "Using redis-#{version}#{meta} on DB #{select}." 15 | 16 | if parseInt(version.replace(/\./g, ""), 10) < 200 17 | console.log "It's recommended to upgrade redis to 2.0.0 or higher!" 18 | 19 | return GLOBAL_REDIS_CLIENT 20 | -------------------------------------------------------------------------------- /src/server/config.sample.coffee: -------------------------------------------------------------------------------- 1 | # customize me: 2 | exports.Configuration = 3 | host: 4 | SCHEME: "http" # used in generating URLs 5 | FQDN: "localhost" 6 | PORT: 8080 7 | USE_PORT_IN_URL: true 8 | 9 | redis: 10 | host: "127.0.0.1" 11 | port: 6379 12 | select: 15 13 | 14 | ssl: 15 | USE_NODE_SSL: false # only necessary if you're not having nginx proxy through to node 16 | PRIVATE_KEY: "/path/to/server.key" 17 | CERTIFICATE: "/path/to/certificate.crt" 18 | 19 | features: 20 | SERVER_NICK: "Server" 21 | irc_server: false # beta atm, you may not want to enable this as server stability isn't guaranteed 22 | 23 | chat: 24 | log: true # keeps a log server-side for participants who may have been offline 25 | 26 | webshot_previews: # requires phantomjs to be installed 27 | enabled: true # http://www.youtube.com/watch?feature=player_detailpage&v=k3-zaTr6OUo#t=23s 28 | PHANTOMJS_PATH: "/usr/bin/phantomjs" # sudo apt-get install phantomjs 29 | 30 | rate_limiting: # slows down spammers 31 | enabled: true 32 | rate: 5.0 # # allowed messages 33 | per: 8000.0 # per # of seconds 34 | 35 | edit: # can users edit sent messages? 36 | enabled: true 37 | allow_unidentified: true # whether anonymous users can edit their messages within the context of the same session 38 | maximum_time_delta: (1000 * 60 * 60 * 2) # after 2 hours, chat messages will not be editable, delete property to enable indefinitely 39 | 40 | server_hosted_file_transfer: 41 | enabled: false 42 | size_limit: "10mb" # nginx user? make sure this matches your nginx configuration: e.g., look for line `client_max_body_size 10M;` 43 | 44 | DEBUG: false 45 | -------------------------------------------------------------------------------- /src/server/extensions/dice.coffee: -------------------------------------------------------------------------------- 1 | module.exports.Dice = class Dice 2 | rollDie: (nFaces) -> 3 | 1 + Math.floor(Math.random()*nFaces) 4 | 5 | parseDice: (diceString) -> 6 | # converts, e.g., '5d20' into the dice representation 7 | # interprets non-matches as '1d20' 8 | type = "1d20" 9 | nDies = "1" 10 | nFaces = "20" 11 | 12 | matches = diceString.match(/(\d+)d(\d+)/) # '50d209' matches = ['50d209', '50', '209'] 13 | if matches?.length == 3 14 | type = matches[0] 15 | nDies = matches[1] 16 | nFaces = matches[2] 17 | 18 | dice = 19 | type: type 20 | nDies: parseInt(nDies, 10) 21 | nFaces: parseInt(nFaces, 10) 22 | 23 | rollDice: (dice) -> 24 | results = [] 25 | 26 | sum = 0; i = 0 27 | while i < dice.nDies 28 | roll = @rollDie(dice.nFaces) 29 | results.push roll 30 | sum += roll 31 | i++ 32 | 33 | result = 34 | dice: dice 35 | sum: sum 36 | rolls: results 37 | 38 | parseAndRollDice: (diceString) -> 39 | @rollDice @parseDice diceString 40 | 41 | formatResult: (diceResult) -> 42 | if diceResult.rolls.length > 1 43 | visualSum = diceResult.rolls.join(" + ") 44 | "rolled #{diceResult.dice.type}: #{visualSum} = #{diceResult.sum}" 45 | else 46 | "rolled #{diceResult.dice.type}: #{diceResult.sum}" 47 | -------------------------------------------------------------------------------- /src/server/sample-gh-payload.json: -------------------------------------------------------------------------------- 1 | { payload: '{"ref":"refs/heads/please-ignore","after":"9ac908d22ba316dda2e819668454f3af6603f9b6","before":"6dd8da1887bee4df398fb483a4490365c5748711","created":false,"deleted":false,"forced":false,"compare":"https://github.com/qq99/echoplexus/compare/6dd8da1887be...9ac908d22ba3","commits":[{"id":"9ac908d22ba316dda2e819668454f3af6603f9b6","distinct":true,"message":"test","timestamp":"2013-12-15T22:26:16-08:00","url":"https://github.com/qq99/echoplexus/commit/9ac908d22ba316dda2e819668454f3af6603f9b6","author":{"name":"Anthony Cameron","email":"anthony.cameron@jadedpixel.com","username":"qq99"},"committer":{"name":"Anthony Cameron","email":"anthony.cameron@jadedpixel.com","username":"qq99"},"added":[".tmp"],"removed":[],"modified":[]}],"head_commit":{"id":"9ac908d22ba316dda2e819668454f3af6603f9b6","distinct":true,"message":"test","timestamp":"2013-12-15T22:26:16-08:00","url":"https://github.com/qq99/echoplexus/commit/9ac908d22ba316dda2e819668454f3af6603f9b6","author":{"name":"Anthony Cameron","email":"anthony.cameron@jadedpixel.com","username":"qq99"},"committer":{"name":"Anthony Cameron","email":"anthony.cameron@jadedpixel.com","username":"qq99"},"added":[".tmp"],"removed":[],"modified":[]},"repository":{"id":10426475,"name":"echoplexus","url":"https://github.com/qq99/echoplexus","description":"Socket.io powered chat, JavaScript REPL, whiteboard, and WebRTC calls","homepage":"https://echoplex.us","watchers":208,"stargazers":208,"forks":18,"fork":false,"size":4615,"owner":{"name":"qq99","email":"ar.cameron@gmail.com"},"private":false,"open_issues":58,"has_issues":true,"has_downloads":true,"has_wiki":true,"language":"JavaScript","created_at":1370111089,"pushed_at":1387175182,"master_branch":"master"},"pusher":{"name":"qq99","email":"ar.cameron@gmail.com"}}' } 2 | -------------------------------------------------------------------------------- /src/server/samples/build_nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./configure \ 4 | --prefix=/usr/share/nginx \ 5 | --sbin-path=/usr/sbin/nginx \ 6 | --conf-path=/etc/nginx/nginx.conf \ 7 | --pid-path=/var/run/nginx.pid \ 8 | --lock-path=/var/lock/nginx.lock \ 9 | --error-log-path=/var/log/nginx/error.log \ 10 | --http-log-path=/var/log/access.log \ 11 | --user=www-data \ 12 | --group=www-data \ 13 | --with-http_spdy_module \ 14 | --with-ipv6 \ 15 | --with-http_ssl_module \ 16 | --with-http_spdy_module \ 17 | --with-http_stub_status_module \ 18 | --with-http_gzip_static_module \ 19 | --add-module=/home/anthony/nginx_modules/headers-more-nginx-module 20 | -------------------------------------------------------------------------------- /src/server/samples/echoplexus.site: -------------------------------------------------------------------------------- 1 | # 2 | # Rewrite any http requests for domain.com to https://domain.com 3 | # 4 | server { 5 | listen 80; 6 | server_name chat.echoplex.us; 7 | return 301 https://chat.echoplex.us$request_uri; 8 | } 9 | 10 | # 11 | # HTTP server definition 12 | # uncomment me if you wish to run a non-SSL installation 13 | # 14 | #server { 15 | # listen 80; 16 | # server_name chat.echoplex.us; 17 | # proxy_buffering off; 18 | # location / { 19 | # proxy_pass http://localhost:8080; 20 | # proxy_http_version 1.1; 21 | # proxy_set_header Upgrade $http_upgrade; 22 | # proxy_set_header Connection $connection_upgrade; 23 | # } 24 | # access_log /var/log/nginx/echoplexus/access.log; 25 | # error_log /var/log/nginx/echoplexus/error.log; 26 | #} 27 | 28 | # 29 | # HTTPS server def'n 30 | # 31 | server { 32 | listen 443; 33 | server_name chat.echoplex.us; 34 | 35 | # This should match what you set @ server/config.coffee for: 36 | # server_hosted_file_transfer.size_limit (note diff units!) 37 | client_max_body_size 10M; 38 | 39 | ssl on; 40 | ssl_certificate /path/to/ssl.crt; 41 | ssl_certificate_key /path/to/ssl.key; 42 | 43 | # enable session resumption to improve https performance 44 | # http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html 45 | ssl_session_cache shared:SSL:50m; 46 | ssl_session_timeout 5m; 47 | 48 | 49 | # enables server-side protection from BEAST attacks 50 | # http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html 51 | ssl_prefer_server_ciphers on; 52 | 53 | # disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0 54 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 55 | 56 | # ciphers chosen for forward secrecy and compatibility 57 | # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html 58 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK'; 59 | 60 | # config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security 61 | # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping 62 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; 63 | 64 | # if you wish to embed, set up a different site for embed.echoplex.us 65 | add_header X-Frame-Options "SAMEORIGIN"; 66 | add_header X-Frame-Options sameorigin; 67 | 68 | proxy_buffering off; # may interfere with websockets if on 69 | 70 | # serve up all static webshots / user uploads via nginx 71 | # don't bother proxying them to express 72 | # !replace `/home/sandbox` with the path of the user running echoplexus! 73 | location /sandbox/ { 74 | autoindex on; 75 | alias /home/sandbox/echoplexus/public/sandbox/; 76 | try_files $uri $uri/ =404; 77 | } 78 | 79 | location / { 80 | proxy_pass http://localhost:8080; 81 | proxy_http_version 1.1; 82 | proxy_set_header Upgrade $http_upgrade; 83 | proxy_set_header Connection $connection_upgrade; 84 | } 85 | access_log /var/log/nginx/echoplexus/access.ssl.log; 86 | error_log /var/log/nginx/echoplexus/error.ssl.log; 87 | 88 | gzip on; 89 | gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript; 90 | gzip_vary on; 91 | gzip_comp_level 9; 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/server/samples/nginx.conf.excerpt: -------------------------------------------------------------------------------- 1 | # within your nginx.conf, you'll need to add the following: 2 | 3 | # ... 4 | 5 | http { 6 | #... 7 | # other stuff 8 | 9 | map $http_upgrade $connection_upgrade { 10 | default upgrade; 11 | '' close; 12 | } 13 | 14 | # ... 15 | # other stuff 16 | } 17 | 18 | # ... 19 | -------------------------------------------------------------------------------- /src/server/utility.coffee: -------------------------------------------------------------------------------- 1 | crypto = require("crypto") 2 | 3 | module.exports.Utility = class Utility 4 | 5 | getSimpleSaltedMD5: (stringToHash, callback) -> 6 | 7 | crypto.randomBytes 64, (err, buf) -> 8 | throw err if err 9 | 10 | salted = buf.toString('hex') + stringToHash 11 | md5 = crypto.createHash('md5') 12 | md5.update(salted) 13 | hash = md5.digest('hex') 14 | 15 | callback?(null, hash) 16 | -------------------------------------------------------------------------------- /src/server/version.coffee: -------------------------------------------------------------------------------- 1 | module.exports.LATEST_SUPPORTED_CLIENT_VERSION = '0.2.6'; 2 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "jasmine", 3 | "before_tests": "browserify tests/tests.coffee -o browserified.js -t coffeeify -t node-underscorify", 4 | "src_files": [ 5 | "public/js/vendor.min.js", 6 | "src/**/*.coffee", 7 | "tests/**/*.coffee", 8 | "tests/**/*.js" 9 | ], 10 | "serve_files": [ 11 | "public/js/vendor.min.js", 12 | "public/js/openpgp.min.js", 13 | "vendor/sinon/lib/sinon.js", 14 | "vendor/sinon/lib/sinon/match.js", 15 | "vendor/sinon/lib/sinon/call.js", 16 | "vendor/sinon/lib/sinon/spy.js", 17 | "vendor/sinon/lib/sinon/mock.js", 18 | "vendor/sinon/lib/sinon/stub.js", 19 | "vendor/chai/chai.js", 20 | "tests/setup.js", 21 | "browserified.js" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tests/client/color_model_test.coffee: -------------------------------------------------------------------------------- 1 | client = require('../../src/client/client.js.coffee') 2 | ColorModel = client.ColorModel 3 | 4 | describe 'ColorModel', -> 5 | beforeEach -> 6 | @subject = new ColorModel 7 | r: 111 8 | g: 222 9 | b: 94 10 | 11 | describe 'constructors', -> 12 | it 'if passed parameters, should initialize with those parameters', -> 13 | expect(@subject.get("r")).to.equal 111 14 | expect(@subject.get("g")).to.equal 222 15 | expect(@subject.get("b")).to.equal 94 16 | 17 | it 'if passed no parameters, it should randomly create a color', -> 18 | mathRandom = spy Math, 'random' 19 | 20 | @subject = new ColorModel 21 | assert (mathRandom.callCount >= 3), "at least 3 random calls for RGB" 22 | 23 | describe '#setFromHex', -> 24 | it 'should properly convert and set from 6-hex string to RGB', -> 25 | @subject.parse('#FF000A') 26 | assert.equal 255, @subject.get('r') 27 | assert.equal 0, @subject.get('g') 28 | assert.equal 10, @subject.get('b') 29 | 30 | it 'should properly convert and set from 3-hex string to RGB', -> 31 | @subject.parse('#666') 32 | assert.equal 102, @subject.get('r') 33 | assert.equal 102, @subject.get('g') 34 | assert.equal 102, @subject.get('b') 35 | 36 | @subject.parse('#ABC') 37 | assert.equal 171, @subject.get('r') 38 | assert.equal 202, @subject.get('g') 39 | assert.equal 188, @subject.get('b') 40 | 41 | it 'should not accept other CSS color formats (yet)', -> 42 | cb = stub() 43 | 44 | @subject.parse('rgba(0,0,0,0.5)', cb) 45 | 46 | assert cb.called, "It didn't call the callback" 47 | assert cb.neverCalledWith(null), "It didn't call the callback with an error" 48 | 49 | describe '#toRGB', -> 50 | it 'should convert to rgb() CSS format correctly', -> 51 | assert.equal 'rgb(111, 222, 94)', @subject.toRGB() 52 | -------------------------------------------------------------------------------- /tests/client/htmlsanitizer_test.coffee: -------------------------------------------------------------------------------- 1 | HTMLSanitizer = require('../../src/client/utility.js.coffee').HTMLSanitizer 2 | 3 | describe 'HTMLSanitizer', -> 4 | beforeEach -> 5 | @subject = new HTMLSanitizer 6 | 7 | it 'strips out unwanted attributes', -> 8 | html = "XSS" 9 | 10 | assert.equal 'XSS', @subject.sanitize(html) 11 | 12 | it 'strips out unwanted tags', -> 13 | html = "

    this is a test

    " 14 | 15 | assert.equal "

    this is a test

    ", @subject.sanitize(html) 16 | 17 | it 'accepts a list of custom whitelisted tags', -> 18 | html = "

    this is a test

    • hi
    " 19 | 20 | assert.equal "

    this is a test

    • hi
    ", @subject.sanitize(html) 21 | assert.equal "

    this is a test

    ", @subject.sanitize(html, ["P", "VIDEO"]) 22 | 23 | it 'accepts a list of custom whitelisted attributes', -> 24 | html = "

    u

    " 25 | 26 | assert.equal "

    u

    ", @subject.sanitize(html, null, ["title"]) 27 | 28 | it 'does not execute common XSS trickery', -> 29 | mock window, "alert" 30 | html = "';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//-->\">'>" 31 | 32 | @subject.sanitize(html) 33 | 34 | assert !window.alert.called 35 | 36 | it 'does not execute scripts in img src', -> 37 | mock window, "alert" 38 | html = "" 39 | 40 | @subject.sanitize(html, ["IMG"], ["src"]) 41 | 42 | assert !window.alert.called 43 | 44 | it 'does not even allow malicious attributes with javascript: to be rendered', -> 45 | html = "" 46 | 47 | assert.equal '', @subject.sanitize(html, ["IMG"], ["src", "onclick"]) 48 | -------------------------------------------------------------------------------- /tests/client/modules/chat/chat_messsage_model_test.coffee: -------------------------------------------------------------------------------- 1 | ChatMessageModel = require('../../../../src/client/modules/chat/ChatMessageModel.js.coffee').ChatMessage 2 | 3 | describe 'ChatMessageModel', -> 4 | stub(ChatMessageModel.prototype, "initialize") 5 | describe 'basic rendering pipeline', -> 6 | it 'does nothing special for a simple body content', -> 7 | @subject = new ChatMessageModel 8 | body: "This is a test" 9 | 10 | @subject.format_body() 11 | 12 | assert.equal "This is a test", @subject.get("formatted_body") 13 | 14 | it 'converts links into hyperlinks', -> 15 | @subject = new ChatMessageModel 16 | body: "some URL http://google.ca" 17 | 18 | @subject.format_body() 19 | 20 | assert.equal 'some URL http://google.ca', @subject.get("formatted_body") 21 | 22 | it 'supports body with mentions and links with @ in them', -> 23 | @subject = new ChatMessageModel 24 | body: "@qq99 here's a link for you https://echoplex.us@foo.com @qq99" 25 | 26 | @subject.format_body() 27 | 28 | assert.equal '@qq99 here's a link for you https://echoplex.us@foo.com @qq99', @subject.get("formatted_body") 29 | 30 | it 'renders quotations properly', -> 31 | @subject = new ChatMessageModel 32 | body: ">>99 I agree >>100 I disagree" 33 | @subject.room = "/foo" 34 | 35 | @subject.format_body() 36 | 37 | assert.equal '>>99 I agree >>100 I disagree', @subject.get("formatted_body") 38 | 39 | it 'defaults to empty array for references', -> 40 | @subject = new ChatMessageModel 41 | assert.deepEqual [], @subject.get("references") 42 | 43 | it 'computes its list of references on render', -> 44 | @subject = new ChatMessageModel 45 | body: ">>99 I agree >>100 I disagree" 46 | @subject.room = "/foo" 47 | 48 | @subject.format_body() 49 | assert.deepEqual [99, 100], @subject.get("references") 50 | -------------------------------------------------------------------------------- /tests/client/utility_test.coffee: -------------------------------------------------------------------------------- 1 | versionStringToNumber = require('../../src/client/utility.js.coffee').versionStringToNumber 2 | 3 | describe 'versionStringToNumber()', -> 4 | 5 | it 'correctly interprets the string as a number', -> 6 | assert.equal 1, versionStringToNumber("0.0.0r1") 7 | assert.equal 230, versionStringToNumber("0.2.3r0") 8 | assert.equal 231, versionStringToNumber("0.2.3r1") 9 | assert.equal 1350, versionStringToNumber("1.3.5r0") 10 | 11 | -------------------------------------------------------------------------------- /tests/server/extensions/dice_test.coffee: -------------------------------------------------------------------------------- 1 | Dice = require('../../../src/server/extensions/dice.coffee').Dice 2 | 3 | describe 'Dice', -> 4 | describe '#rollDie', -> 5 | it 'should give correct results for a variety of faces', -> 6 | i = 0 7 | while i < 1000 8 | assert.equal true, (Dice::rollDie(20) <= 20) 9 | i += 1 10 | 11 | describe '#parseDice', -> 12 | it 'assumes 1d20 on nonsensical input', -> 13 | assert.deepEqual {type: "1d20", nDies: 1, nFaces: 20}, Dice::parseDice("foobar") 14 | 15 | it 'should parse different types of dice correctly', -> 16 | assert.deepEqual {type: "5d10", nDies: 5, nFaces: 10}, Dice::parseDice("5d10") 17 | assert.deepEqual {type: "3d100", nDies: 3, nFaces: 100}, Dice::parseDice("3d100") 18 | assert.deepEqual {type: "1d20", nDies: 1, nFaces: 20}, Dice::parseDice("1d20") 19 | 20 | describe '#rollDice', -> 21 | it 'should correctly roll dice', -> 22 | dice = Dice::parseDice("2d10") 23 | 24 | i = 0 25 | while i < 1000 26 | result = Dice::rollDice(dice) 27 | assert.deepEqual dice, result.dice 28 | assert.equal true, result.sum <= (2*10) 29 | 30 | sumOfRolls = 0 31 | for roll in result.rolls 32 | sumOfRolls += roll 33 | 34 | assert.equal sumOfRolls, result.sum 35 | 36 | i += 1 37 | 38 | describe '#formatResult', -> 39 | it 'properly formats our dice roll result', -> 40 | mockResult = 41 | dice: 42 | type: "5d20" 43 | nDies: 5 44 | nFaces: 20 45 | rolls: [1,2,4,8,10] 46 | sum: 25 47 | 48 | assert.equal "rolled 5d20: 1 + 2 + 4 + 8 + 10 = 25", Dice::formatResult(mockResult) 49 | 50 | it 'formats results nicely when there was only a single die', -> 51 | mockResult = 52 | dice: 53 | type: "1d20" 54 | nDies: 1 55 | nFaces: 20 56 | rolls: [5] 57 | sum: 5 58 | 59 | assert.equal "rolled 1d20: 5", Dice::formatResult(mockResult) 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | expect = chai.expect; 2 | assert = chai.assert; 3 | spy = sinon.spy; 4 | mock = sinon.mock; 5 | stub = sinon.stub; 6 | -------------------------------------------------------------------------------- /tests/tests.coffee: -------------------------------------------------------------------------------- 1 | if Storage # extend the local storage protoype if it exists 2 | Storage::setObj = (key, obj) -> 3 | localStorage.setItem key, JSON.stringify(obj) 4 | Storage::getObj = (key) -> 5 | JSON.parse localStorage.getItem(key) 6 | 7 | require('./client/client_model_test.coffee') 8 | require('./client/color_model_test.coffee') 9 | require('./client/htmlsanitizer_test.coffee') 10 | require('./client/utility_test.coffee') 11 | require('./client/regex_test.coffee') 12 | # chat 13 | require('./client/modules/chat/chat_client_test.coffee') 14 | require('./client/modules/chat/chat_messsage_model_test.coffee') 15 | require('./client/modules/chat/log_test.coffee') 16 | require('./server/extensions/dice_test.coffee') 17 | # call 18 | require('./client/modules/call/call_client_test.coffee') 19 | 20 | --------------------------------------------------------------------------------