[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [snowflake/master] Lightly massage some of the generated JavaScript



commit 1867a3f121b5ea032466675f8ea3d00a134acd9a
Author: Arlo Breault <arlolra@xxxxxxxxx>
Date:   Sat Jul 6 15:20:07 2019 +0200

    Lightly massage some of the generated JavaScript
---
 proxy/Cakefile.js            |  42 +++--
 proxy/broker.js              | 188 +++++++++----------
 proxy/config.js              |  57 +++---
 proxy/init-badge.js          |  16 +-
 proxy/init-node.js           |  14 +-
 proxy/init-webext.js         |  20 +-
 proxy/proxypair.js           | 432 +++++++++++++++++++++----------------------
 proxy/shims.js               |   5 +-
 proxy/snowflake.js           | 298 +++++++++++++++--------------
 proxy/spec/broker.spec.js    |  58 +++---
 proxy/spec/init.spec.js      |  19 +-
 proxy/spec/proxypair.spec.js |  51 +++--
 proxy/spec/snowflake.spec.js |  71 +++----
 proxy/spec/ui.spec.js        |  26 +--
 proxy/spec/util.spec.js      |  68 ++++---
 proxy/spec/websocket.spec.js |  23 ++-
 proxy/ui.js                  | 299 ++++++++++++++----------------
 proxy/util.js                | 148 +++++++--------
 proxy/websocket.js           | 112 ++++++-----
 19 files changed, 972 insertions(+), 975 deletions(-)

diff --git a/proxy/Cakefile.js b/proxy/Cakefile.js
index c7bc625..91ceea4 100644
--- a/proxy/Cakefile.js
+++ b/proxy/Cakefile.js
@@ -1,26 +1,44 @@
-// Generated by CoffeeScript 2.4.1
-var FILES, FILES_SPEC, INITS, OUTFILE, STATIC, compileCoffee, copyStaticFiles, exec, execSync, fs, spawn;
 
-fs = require('fs');
-
-({exec, spawn, execSync} = require('child_process'));
+var fs = require('fs');
+var { exec, spawn, execSync } = require('child_process');
 
 // All coffeescript files required.
-FILES = ['broker.coffee', 'config.coffee', 'proxypair.coffee', 'snowflake.coffee', 'ui.coffee', 'util.coffee', 'websocket.coffee', 'shims.coffee'];
+var FILES = [
+  'broker.coffee',
+  'config.coffee',
+  'proxypair.coffee',
+  'snowflake.coffee',
+  'ui.coffee',
+  'util.coffee',
+  'websocket.coffee',
+  'shims.coffee'
+];
 
-INITS = ['init-badge.coffee', 'init-node.coffee', 'init-webext.coffee'];
+var INITS = [
+  'init-badge.coffee',
+  'init-node.coffee',
+  'init-webext.coffee'
+];
 
-FILES_SPEC = ['spec/broker.spec.coffee', 'spec/init.spec.coffee', 'spec/proxypair.spec.coffee', 'spec/snowflake.spec.coffee', 'spec/ui.spec.coffee', 'spec/util.spec.coffee', 'spec/websocket.spec.coffee'];
+var FILES_SPEC = [
+  'spec/broker.spec.coffee',
+  'spec/init.spec.coffee',
+  'spec/proxypair.spec.coffee',
+  'spec/snowflake.spec.coffee',
+  'spec/ui.spec.coffee',
+  'spec/util.spec.coffee',
+  'spec/websocket.spec.coffee'
+];
 
-OUTFILE = 'snowflake.js';
+var OUTFILE = 'snowflake.js';
 
-STATIC = 'static';
+var STATIC = 'static';
 
-copyStaticFiles = function() {
+var copyStaticFiles = function() {
   return exec('cp ' + STATIC + '/* build/');
 };
 
-compileCoffee = function(outDir, init) {
+var compileCoffee = function(outDir, init) {
   var files;
   files = FILES.concat('init-' + init + '.coffee');
   return exec('cat ' + files.join(' ') + ' | coffee -cs > ' + outDir + '/' + OUTFILE, function(err, stdout, stderr) {
diff --git a/proxy/broker.js b/proxy/broker.js
index f7af5bb..4e287b4 100644
--- a/proxy/broker.js
+++ b/proxy/broker.js
@@ -1,73 +1,44 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Communication with the snowflake broker.
 
 Browser snowflakes must register with the broker in order
 to get assigned to clients.
 */
-var Broker;
 
-Broker = (function() {
-  // Represents a broker running remotely.
-  class Broker {
-    // When interacting with the Broker, snowflake must generate a unique session
-    // ID so the Broker can keep track of each proxy's signalling channels.
-    // On construction, this Broker object does not do anything until
-    // |getClientOffer| is called.
-    constructor(url) {
-      // Promises some client SDP Offer.
-      // Registers this Snowflake with the broker using an HTTP POST request, and
-      // waits for a response containing some client offer that the Broker chooses
-      // for this proxy..
-      // TODO: Actually support multiple clients.
-      this.getClientOffer = this.getClientOffer.bind(this);
-      // urlSuffix for the broker is different depending on what action
-      // is desired.
-      this._postRequest = this._postRequest.bind(this);
-      this.url = url;
-      this.clients = 0;
-      if (0 === this.url.indexOf('localhost', 0)) {
-        // Ensure url has the right protocol + trailing slash.
-        this.url = 'http://' + this.url;
-      }
-      if (0 !== this.url.indexOf('http', 0)) {
-        this.url = 'https://' + this.url;
-      }
-      if ('/' !== this.url.substr(-1)) {
-        this.url += '/';
-      }
-    }
+// Represents a broker running remotely.
+class Broker {
 
-    getClientOffer(id) {
-      return new Promise((fulfill, reject) => {
-        var xhr;
-        xhr = new XMLHttpRequest();
-        xhr.onreadystatechange = function() {
-          if (xhr.DONE !== xhr.readyState) {
-            return;
-          }
-          switch (xhr.status) {
-            case Broker.STATUS.OK:
-              return fulfill(xhr.responseText); // Should contain offer.
-            case Broker.STATUS.GATEWAY_TIMEOUT:
-              return reject(Broker.MESSAGE.TIMEOUT);
-            default:
-              log('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
-              snowflake.ui.setStatus(' failure. Please refresh.');
-              return reject(Broker.MESSAGE.UNEXPECTED);
-          }
-        };
-        this._xhr = xhr; // Used by spec to fake async Broker interaction
-        return this._postRequest(id, xhr, 'proxy', id);
-      });
+  // When interacting with the Broker, snowflake must generate a unique session
+  // ID so the Broker can keep track of each proxy's signalling channels.
+  // On construction, this Broker object does not do anything until
+  // |getClientOffer| is called.
+  constructor(url) {
+    // Promises some client SDP Offer.
+    // Registers this Snowflake with the broker using an HTTP POST request, and
+    // waits for a response containing some client offer that the Broker chooses
+    // for this proxy..
+    // TODO: Actually support multiple clients.
+    this.getClientOffer = this.getClientOffer.bind(this);
+    // urlSuffix for the broker is different depending on what action
+    // is desired.
+    this._postRequest = this._postRequest.bind(this);
+    this.url = url;
+    this.clients = 0;
+    if (0 === this.url.indexOf('localhost', 0)) {
+      // Ensure url has the right protocol + trailing slash.
+      this.url = 'http://' + this.url;
+    }
+    if (0 !== this.url.indexOf('http', 0)) {
+      this.url = 'https://' + this.url;
+    }
+    if ('/' !== this.url.substr(-1)) {
+      this.url += '/';
     }
+  }
 
-    // Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
-    // Sends it back to the broker, which passes it to back to the original client.
-    sendAnswer(id, answer) {
+  getClientOffer(id) {
+    return new Promise((fulfill, reject) => {
       var xhr;
-      dbg(id + ' - Sending answer back to broker...\n');
-      dbg(answer.sdp);
       xhr = new XMLHttpRequest();
       xhr.onreadystatechange = function() {
         if (xhr.DONE !== xhr.readyState) {
@@ -75,52 +46,75 @@ Broker = (function() {
         }
         switch (xhr.status) {
           case Broker.STATUS.OK:
-            dbg('Broker: Successfully replied with answer.');
-            return dbg(xhr.responseText);
-          case Broker.STATUS.GONE:
-            return dbg('Broker: No longer valid to reply with answer.');
+            return fulfill(xhr.responseText); // Should contain offer.
+          case Broker.STATUS.GATEWAY_TIMEOUT:
+            return reject(Broker.MESSAGE.TIMEOUT);
           default:
-            dbg('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
-            return snowflake.ui.setStatus(' failure. Please refresh.');
+            log('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
+            snowflake.ui.setStatus(' failure. Please refresh.');
+            return reject(Broker.MESSAGE.UNEXPECTED);
         }
       };
-      return this._postRequest(id, xhr, 'answer', JSON.stringify(answer));
-    }
+      this._xhr = xhr; // Used by spec to fake async Broker interaction
+      return this._postRequest(id, xhr, 'proxy', id);
+    });
+  }
 
-    _postRequest(id, xhr, urlSuffix, payload) {
-      var err;
-      try {
-        xhr.open('POST', this.url + urlSuffix);
-        xhr.setRequestHeader('X-Session-ID', id);
-      } catch (error) {
-        err = error;
-        /*
-        An exception happens here when, for example, NoScript allows the domain
-        on which the proxy badge runs, but not the domain to which it's trying
-        to make the HTTP xhr. The exception message is like "Component
-        returned failure code: 0x805e0006 [nsIXMLHttpRequest.open]" on Firefox.
-        */
-        log('Broker: exception while connecting: ' + err.message);
+  // Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
+  // Sends it back to the broker, which passes it to back to the original client.
+  sendAnswer(id, answer) {
+    var xhr;
+    dbg(id + ' - Sending answer back to broker...\n');
+    dbg(answer.sdp);
+    xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = function() {
+      if (xhr.DONE !== xhr.readyState) {
         return;
       }
-      return xhr.send(payload);
-    }
-
-  };
+      switch (xhr.status) {
+        case Broker.STATUS.OK:
+          dbg('Broker: Successfully replied with answer.');
+          return dbg(xhr.responseText);
+        case Broker.STATUS.GONE:
+          return dbg('Broker: No longer valid to reply with answer.');
+        default:
+          dbg('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
+          return snowflake.ui.setStatus(' failure. Please refresh.');
+      }
+    };
+    return this._postRequest(id, xhr, 'answer', JSON.stringify(answer));
+  }
 
-  Broker.STATUS = {
-    OK: 200,
-    GONE: 410,
-    GATEWAY_TIMEOUT: 504
-  };
+  _postRequest(id, xhr, urlSuffix, payload) {
+    var err;
+    try {
+      xhr.open('POST', this.url + urlSuffix);
+      xhr.setRequestHeader('X-Session-ID', id);
+    } catch (error) {
+      err = error;
+      /*
+      An exception happens here when, for example, NoScript allows the domain
+      on which the proxy badge runs, but not the domain to which it's trying
+      to make the HTTP xhr. The exception message is like "Component
+      returned failure code: 0x805e0006 [nsIXMLHttpRequest.open]" on Firefox.
+      */
+      log('Broker: exception while connecting: ' + err.message);
+      return;
+    }
+    return xhr.send(payload);
+  }
 
-  Broker.MESSAGE = {
-    TIMEOUT: 'Timed out waiting for a client offer.',
-    UNEXPECTED: 'Unexpected status.'
-  };
+};
 
-  Broker.prototype.clients = 0;
+Broker.STATUS = {
+  OK: 200,
+  GONE: 410,
+  GATEWAY_TIMEOUT: 504
+};
 
-  return Broker;
+Broker.MESSAGE = {
+  TIMEOUT: 'Timed out waiting for a client offer.',
+  UNEXPECTED: 'Unexpected status.'
+};
 
-}).call(this);
+Broker.prototype.clients = 0;
diff --git a/proxy/config.js b/proxy/config.js
index 69d7b75..316f3b0 100644
--- a/proxy/config.js
+++ b/proxy/config.js
@@ -1,43 +1,36 @@
-// Generated by CoffeeScript 2.4.1
-var Config;
 
-Config = (function() {
-  class Config {};
+class Config {};
 
-  Config.prototype.brokerUrl = 'snowflake-broker.bamsoftware.com';
+Config.prototype.brokerUrl = 'snowflake-broker.bamsoftware.com';
 
-  Config.prototype.relayAddr = {
-    host: 'snowflake.bamsoftware.com',
-    port: '443'
-  };
+Config.prototype.relayAddr = {
+  host: 'snowflake.bamsoftware.com',
+  port: '443'
+};
 
-  // Original non-wss relay:
-  // host: '192.81.135.242'
-  // port: 9902
-  Config.prototype.cookieName = "snowflake-allow";
+// Original non-wss relay:
+// host: '192.81.135.242'
+// port: 9902
+Config.prototype.cookieName = "snowflake-allow";
 
-  // Bytes per second. Set to undefined to disable limit.
-  Config.prototype.rateLimitBytes = void 0;
+// Bytes per second. Set to undefined to disable limit.
+Config.prototype.rateLimitBytes = void 0;
 
-  Config.prototype.minRateLimit = 10 * 1024;
+Config.prototype.minRateLimit = 10 * 1024;
 
-  Config.prototype.rateLimitHistory = 5.0;
+Config.prototype.rateLimitHistory = 5.0;
 
-  Config.prototype.defaultBrokerPollInterval = 5.0 * 1000;
+Config.prototype.defaultBrokerPollInterval = 5.0 * 1000;
 
-  Config.prototype.maxNumClients = 1;
+Config.prototype.maxNumClients = 1;
 
-  Config.prototype.connectionsPerClient = 1;
+Config.prototype.connectionsPerClient = 1;
 
-  // TODO: Different ICE servers.
-  Config.prototype.pcConfig = {
-    iceServers: [
-      {
-        urls: ['stun:stun.l.google.com:19302']
-      }
-    ]
-  };
-
-  return Config;
-
-}).call(this);
+// TODO: Different ICE servers.
+Config.prototype.pcConfig = {
+  iceServers: [
+    {
+      urls: ['stun:stun.l.google.com:19302']
+    }
+  ]
+};
diff --git a/proxy/init-badge.js b/proxy/init-badge.js
index 136835b..7e5277f 100644
--- a/proxy/init-badge.js
+++ b/proxy/init-badge.js
@@ -1,37 +1,35 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Entry point.
 */
-var dbg, debug, init, log, query, silenceNotifications, snowflake;
 
 if (((typeof TESTING === "undefined" || TESTING === null) || !TESTING) && !Util.featureDetect()) {
   console.log('webrtc feature not detected. shutting down');
   return;
 }
 
-snowflake = null;
+var snowflake = null;
 
-query = Query.parse(location);
+var query = Query.parse(location);
 
-debug = Params.getBool(query, 'debug', false);
+var debug = Params.getBool(query, 'debug', false);
 
-silenceNotifications = Params.getBool(query, 'silent', false);
+var silenceNotifications = Params.getBool(query, 'silent', false);
 
 // Log to both console and UI if applicable.
 // Requires that the snowflake and UI objects are hooked up in order to
 // log to console.
-log = function(msg) {
+var log = function(msg) {
   console.log('Snowflake: ' + msg);
   return snowflake != null ? snowflake.ui.log(msg) : void 0;
 };
 
-dbg = function(msg) {
+var dbg = function(msg) {
   if (debug || ((snowflake != null ? snowflake.ui : void 0) instanceof DebugUI)) {
     return log(msg);
   }
 };
 
-init = function() {
+var init = function() {
   var broker, config, ui;
   config = new Config;
   if ('off' !== query['ratelimit']) {
diff --git a/proxy/init-node.js b/proxy/init-node.js
index c6e2e52..9efd486 100644
--- a/proxy/init-node.js
+++ b/proxy/init-node.js
@@ -1,22 +1,20 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Entry point.
 */
-var broker, config, dbg, log, snowflake, ui;
 
-config = new Config;
+var config = new Config;
 
-ui = new UI();
+var ui = new UI();
 
-broker = new Broker(config.brokerUrl);
+var broker = new Broker(config.brokerUrl);
 
-snowflake = new Snowflake(config, ui, broker);
+var snowflake = new Snowflake(config, ui, broker);
 
-log = function(msg) {
+var log = function(msg) {
   return console.log('Snowflake: ' + msg);
 };
 
-dbg = log;
+var dbg = log;
 
 log('== snowflake proxy ==');
 
diff --git a/proxy/init-webext.js b/proxy/init-webext.js
index 4b23403..89072b8 100644
--- a/proxy/init-webext.js
+++ b/proxy/init-webext.js
@@ -1,28 +1,26 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Entry point.
 */
-var broker, config, dbg, debug, init, log, snowflake, ui, update;
 
-debug = false;
+var debug = false;
 
-snowflake = null;
+var snowflake = null;
 
-config = null;
+var config = null;
 
-broker = null;
+var broker = null;
 
-ui = null;
+var ui = null;
 
 // Log to both console and UI if applicable.
 // Requires that the snowflake and UI objects are hooked up in order to
 // log to console.
-log = function(msg) {
+var log = function(msg) {
   console.log('Snowflake: ' + msg);
   return snowflake != null ? snowflake.ui.log(msg) : void 0;
 };
 
-dbg = function(msg) {
+var dbg = function(msg) {
   if (debug) {
     return log(msg);
   }
@@ -37,7 +35,7 @@ if (!Util.featureDetect()) {
   return;
 }
 
-init = function() {
+var init = function() {
   config = new Config;
   ui = new WebExtUI();
   broker = new Broker(config.brokerUrl);
@@ -46,7 +44,7 @@ init = function() {
   return ui.initToggle();
 };
 
-update = function() {
+var update = function() {
   if (!ui.enabled) {
     // Do not activate the proxy if any number of conditions are true.
     snowflake.disable();
diff --git a/proxy/proxypair.js b/proxy/proxypair.js
index 40f7b38..c162807 100644
--- a/proxy/proxypair.js
+++ b/proxy/proxypair.js
@@ -1,4 +1,3 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Represents a single:
 
@@ -7,256 +6,251 @@ Represents a single:
 Every ProxyPair has a Snowflake ID, which is necessary when responding to the
 Broker with an WebRTC answer.
 */
-var ProxyPair;
 
-ProxyPair = (function() {
-  class ProxyPair {
-    /*
-    Constructs a ProxyPair where:
-    - @relayAddr is the destination relay
-    - @rateLimit specifies a rate limit on traffic
-    */
-    constructor(relayAddr, rateLimit, pcConfig) {
-      // Given a WebRTC DataChannel, prepare callbacks.
-      this.prepareDataChannel = this.prepareDataChannel.bind(this);
-      // Assumes WebRTC datachannel is connected.
-      this.connectRelay = this.connectRelay.bind(this);
-      // WebRTC --> websocket
-      this.onClientToRelayMessage = this.onClientToRelayMessage.bind(this);
-      // websocket --> WebRTC
-      this.onRelayToClientMessage = this.onRelayToClientMessage.bind(this);
-      this.onError = this.onError.bind(this);
-      // Send as much data in both directions as the rate limit currently allows.
-      this.flush = this.flush.bind(this);
-      this.relayAddr = relayAddr;
-      this.rateLimit = rateLimit;
-      this.pcConfig = pcConfig;
-      this.id = Util.genSnowflakeID();
-      this.c2rSchedule = [];
-      this.r2cSchedule = [];
-    }
-
-    // Prepare a WebRTC PeerConnection and await for an SDP offer.
-    begin() {
-      this.pc = new PeerConnection(this.pcConfig, {
-        optional: [
-          {
-            DtlsSrtpKeyAgreement: true
-          },
-          {
-            RtpDataChannels: false
-          }
-        ]
-      });
-      this.pc.onicecandidate = (evt) => {
-        // Browser sends a null candidate once the ICE gathering completes.
-        if (null === evt.candidate) {
-          // TODO: Use a promise.all to tell Snowflake about all offers at once,
-          // once multiple proxypairs are supported.
-          dbg('Finished gathering ICE candidates.');
-          return snowflake.broker.sendAnswer(this.id, this.pc.localDescription);
-        }
-      };
-      // OnDataChannel triggered remotely from the client when connection succeeds.
-      return this.pc.ondatachannel = (dc) => {
-        var channel;
-        channel = dc.channel;
-        dbg('Data Channel established...');
-        this.prepareDataChannel(channel);
-        return this.client = channel;
-      };
-    }
-
-    receiveWebRTCOffer(offer) {
-      var e, err;
-      if ('offer' !== offer.type) {
-        log('Invalid SDP received -- was not an offer.');
-        return false;
-      }
-      try {
-        err = this.pc.setRemoteDescription(offer);
-      } catch (error) {
-        e = error;
-        log('Invalid SDP message.');
-        return false;
-      }
-      dbg('SDP ' + offer.type + ' successfully received.');
-      return true;
-    }
+class ProxyPair {
 
-    prepareDataChannel(channel) {
-      channel.onopen = () => {
-        log('WebRTC DataChannel opened!');
-        snowflake.state = Snowflake.MODE.WEBRTC_READY;
-        snowflake.ui.setActive(true);
-        // This is the point when the WebRTC datachannel is done, so the next step
-        // is to establish websocket to the server.
-        return this.connectRelay();
-      };
-      channel.onclose = () => {
-        log('WebRTC DataChannel closed.');
-        snowflake.ui.setStatus('disconnected by webrtc.');
-        snowflake.ui.setActive(false);
-        snowflake.state = Snowflake.MODE.INIT;
-        this.flush();
-        return this.close();
-      };
-      channel.onerror = function() {
-        return log('Data channel error!');
-      };
-      channel.binaryType = "arraybuffer";
-      return channel.onmessage = this.onClientToRelayMessage;
-    }
+  /*
+  Constructs a ProxyPair where:
+  - @relayAddr is the destination relay
+  - @rateLimit specifies a rate limit on traffic
+  */
+  constructor(relayAddr, rateLimit, pcConfig) {
+    // Given a WebRTC DataChannel, prepare callbacks.
+    this.prepareDataChannel = this.prepareDataChannel.bind(this);
+    // Assumes WebRTC datachannel is connected.
+    this.connectRelay = this.connectRelay.bind(this);
+    // WebRTC --> websocket
+    this.onClientToRelayMessage = this.onClientToRelayMessage.bind(this);
+    // websocket --> WebRTC
+    this.onRelayToClientMessage = this.onRelayToClientMessage.bind(this);
+    this.onError = this.onError.bind(this);
+    // Send as much data in both directions as the rate limit currently allows.
+    this.flush = this.flush.bind(this);
+    this.relayAddr = relayAddr;
+    this.rateLimit = rateLimit;
+    this.pcConfig = pcConfig;
+    this.id = Util.genSnowflakeID();
+    this.c2rSchedule = [];
+    this.r2cSchedule = [];
+  }
 
-    connectRelay() {
-      var params, peer_ip, ref;
-      dbg('Connecting to relay...');
-      // Get a remote IP address from the PeerConnection, if possible. Add it to
-      // the WebSocket URL's query string if available.
-      // MDN marks remoteDescription as "experimental". However the other two
-      // options, currentRemoteDescription and pendingRemoteDescription, which
-      // are not marked experimental, were undefined when I tried them in Firefox
-      // 52.2.0.
-      // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/remoteDescription
-      peer_ip = Parse.ipFromSDP((ref = this.pc.remoteDescription) != null ? ref.sdp : void 0);
-      params = [];
-      if (peer_ip != null) {
-        params.push(["client_ip", peer_ip]);
-      }
-      this.relay = WS.makeWebsocket(this.relayAddr, params);
-      this.relay.label = 'websocket-relay';
-      this.relay.onopen = () => {
-        if (this.timer) {
-          clearTimeout(this.timer);
-          this.timer = 0;
+  // Prepare a WebRTC PeerConnection and await for an SDP offer.
+  begin() {
+    this.pc = new PeerConnection(this.pcConfig, {
+      optional: [
+        {
+          DtlsSrtpKeyAgreement: true
+        },
+        {
+          RtpDataChannels: false
         }
-        log(this.relay.label + ' connected!');
-        return snowflake.ui.setStatus('connected');
-      };
-      this.relay.onclose = () => {
-        log(this.relay.label + ' closed.');
-        snowflake.ui.setStatus('disconnected.');
-        snowflake.ui.setActive(false);
-        snowflake.state = Snowflake.MODE.INIT;
-        this.flush();
-        return this.close();
-      };
-      this.relay.onerror = this.onError;
-      this.relay.onmessage = this.onRelayToClientMessage;
-      // TODO: Better websocket timeout handling.
-      return this.timer = setTimeout((() => {
-        if (0 === this.timer) {
-          return;
-        }
-        log(this.relay.label + ' timed out connecting.');
-        return this.relay.onclose();
-      }), 5000);
-    }
+      ]
+    });
+    this.pc.onicecandidate = (evt) => {
+      // Browser sends a null candidate once the ICE gathering completes.
+      if (null === evt.candidate) {
+        // TODO: Use a promise.all to tell Snowflake about all offers at once,
+        // once multiple proxypairs are supported.
+        dbg('Finished gathering ICE candidates.');
+        return snowflake.broker.sendAnswer(this.id, this.pc.localDescription);
+      }
+    };
+    // OnDataChannel triggered remotely from the client when connection succeeds.
+    return this.pc.ondatachannel = (dc) => {
+      var channel;
+      channel = dc.channel;
+      dbg('Data Channel established...');
+      this.prepareDataChannel(channel);
+      return this.client = channel;
+    };
+  }
 
-    onClientToRelayMessage(msg) {
-      dbg('WebRTC --> websocket data: ' + msg.data.byteLength + ' bytes');
-      this.c2rSchedule.push(msg.data);
-      return this.flush();
+  receiveWebRTCOffer(offer) {
+    var e, err;
+    if ('offer' !== offer.type) {
+      log('Invalid SDP received -- was not an offer.');
+      return false;
     }
-
-    onRelayToClientMessage(event) {
-      dbg('websocket --> WebRTC data: ' + event.data.byteLength + ' bytes');
-      this.r2cSchedule.push(event.data);
-      return this.flush();
+    try {
+      err = this.pc.setRemoteDescription(offer);
+    } catch (error) {
+      e = error;
+      log('Invalid SDP message.');
+      return false;
     }
+    dbg('SDP ' + offer.type + ' successfully received.');
+    return true;
+  }
 
-    onError(event) {
-      var ws;
-      ws = event.target;
-      log(ws.label + ' error.');
+  prepareDataChannel(channel) {
+    channel.onopen = () => {
+      log('WebRTC DataChannel opened!');
+      snowflake.state = Snowflake.MODE.WEBRTC_READY;
+      snowflake.ui.setActive(true);
+      // This is the point when the WebRTC datachannel is done, so the next step
+      // is to establish websocket to the server.
+      return this.connectRelay();
+    };
+    channel.onclose = () => {
+      log('WebRTC DataChannel closed.');
+      snowflake.ui.setStatus('disconnected by webrtc.');
+      snowflake.ui.setActive(false);
+      snowflake.state = Snowflake.MODE.INIT;
+      this.flush();
       return this.close();
-    }
+    };
+    channel.onerror = function() {
+      return log('Data channel error!');
+    };
+    channel.binaryType = "arraybuffer";
+    return channel.onmessage = this.onClientToRelayMessage;
+  }
 
-    // Close both WebRTC and websocket.
-    close() {
-      var relay;
+  connectRelay() {
+    var params, peer_ip, ref;
+    dbg('Connecting to relay...');
+    // Get a remote IP address from the PeerConnection, if possible. Add it to
+    // the WebSocket URL's query string if available.
+    // MDN marks remoteDescription as "experimental". However the other two
+    // options, currentRemoteDescription and pendingRemoteDescription, which
+    // are not marked experimental, were undefined when I tried them in Firefox
+    // 52.2.0.
+    // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/remoteDescription
+    peer_ip = Parse.ipFromSDP((ref = this.pc.remoteDescription) != null ? ref.sdp : void 0);
+    params = [];
+    if (peer_ip != null) {
+      params.push(["client_ip", peer_ip]);
+    }
+    this.relay = WS.makeWebsocket(this.relayAddr, params);
+    this.relay.label = 'websocket-relay';
+    this.relay.onopen = () => {
       if (this.timer) {
         clearTimeout(this.timer);
         this.timer = 0;
       }
-      this.running = false;
-      if (this.webrtcIsReady()) {
-        this.client.close();
-      }
-      if (this.relayIsReady()) {
-        this.relay.close();
+      log(this.relay.label + ' connected!');
+      return snowflake.ui.setStatus('connected');
+    };
+    this.relay.onclose = () => {
+      log(this.relay.label + ' closed.');
+      snowflake.ui.setStatus('disconnected.');
+      snowflake.ui.setActive(false);
+      snowflake.state = Snowflake.MODE.INIT;
+      this.flush();
+      return this.close();
+    };
+    this.relay.onerror = this.onError;
+    this.relay.onmessage = this.onRelayToClientMessage;
+    // TODO: Better websocket timeout handling.
+    return this.timer = setTimeout((() => {
+      if (0 === this.timer) {
+        return;
       }
-      relay = null;
-      return this.onCleanup();
+      log(this.relay.label + ' timed out connecting.');
+      return this.relay.onclose();
+    }), 5000);
+  }
+
+  onClientToRelayMessage(msg) {
+    dbg('WebRTC --> websocket data: ' + msg.data.byteLength + ' bytes');
+    this.c2rSchedule.push(msg.data);
+    return this.flush();
+  }
+
+  onRelayToClientMessage(event) {
+    dbg('websocket --> WebRTC data: ' + event.data.byteLength + ' bytes');
+    this.r2cSchedule.push(event.data);
+    return this.flush();
+  }
+
+  onError(event) {
+    var ws;
+    ws = event.target;
+    log(ws.label + ' error.');
+    return this.close();
+  }
+
+  // Close both WebRTC and websocket.
+  close() {
+    var relay;
+    if (this.timer) {
+      clearTimeout(this.timer);
+      this.timer = 0;
+    }
+    this.running = false;
+    if (this.webrtcIsReady()) {
+      this.client.close();
     }
+    if (this.relayIsReady()) {
+      this.relay.close();
+    }
+    relay = null;
+    return this.onCleanup();
+  }
 
-    flush() {
-      var busy, checkChunks;
-      if (this.flush_timeout_id) {
-        clearTimeout(this.flush_timeout_id);
-      }
-      this.flush_timeout_id = null;
-      busy = true;
-      checkChunks = () => {
-        var chunk;
-        busy = false;
-        // WebRTC --> websocket
-        if (this.relayIsReady() && this.relay.bufferedAmount < this.MAX_BUFFER && this.c2rSchedule.length > 0) {
-          chunk = this.c2rSchedule.shift();
-          this.rateLimit.update(chunk.byteLength);
-          this.relay.send(chunk);
-          busy = true;
-        }
-        // websocket --> WebRTC
-        if (this.webrtcIsReady() && this.client.bufferedAmount < this.MAX_BUFFER && this.r2cSchedule.length > 0) {
-          chunk = this.r2cSchedule.shift();
-          this.rateLimit.update(chunk.byteLength);
-          this.client.send(chunk);
-          return busy = true;
-        }
-      };
-      while (busy && !this.rateLimit.isLimited()) {
-        checkChunks();
+  flush() {
+    var busy, checkChunks;
+    if (this.flush_timeout_id) {
+      clearTimeout(this.flush_timeout_id);
+    }
+    this.flush_timeout_id = null;
+    busy = true;
+    checkChunks = () => {
+      var chunk;
+      busy = false;
+      // WebRTC --> websocket
+      if (this.relayIsReady() && this.relay.bufferedAmount < this.MAX_BUFFER && this.c2rSchedule.length > 0) {
+        chunk = this.c2rSchedule.shift();
+        this.rateLimit.update(chunk.byteLength);
+        this.relay.send(chunk);
+        busy = true;
       }
-      if (this.r2cSchedule.length > 0 || this.c2rSchedule.length > 0 || (this.relayIsReady() && this.relay.bufferedAmount > 0) || (this.webrtcIsReady() && this.client.bufferedAmount > 0)) {
-        return this.flush_timeout_id = setTimeout(this.flush, this.rateLimit.when() * 1000);
+      // websocket --> WebRTC
+      if (this.webrtcIsReady() && this.client.bufferedAmount < this.MAX_BUFFER && this.r2cSchedule.length > 0) {
+        chunk = this.r2cSchedule.shift();
+        this.rateLimit.update(chunk.byteLength);
+        this.client.send(chunk);
+        return busy = true;
       }
+    };
+    while (busy && !this.rateLimit.isLimited()) {
+      checkChunks();
     }
-
-    webrtcIsReady() {
-      return null !== this.client && 'open' === this.client.readyState;
-    }
-
-    relayIsReady() {
-      return (null !== this.relay) && (WebSocket.OPEN === this.relay.readyState);
+    if (this.r2cSchedule.length > 0 || this.c2rSchedule.length > 0 || (this.relayIsReady() && this.relay.bufferedAmount > 0) || (this.webrtcIsReady() && this.client.bufferedAmount > 0)) {
+      return this.flush_timeout_id = setTimeout(this.flush, this.rateLimit.when() * 1000);
     }
+  }
 
-    isClosed(ws) {
-      return void 0 === ws || WebSocket.CLOSED === ws.readyState;
-    }
+  webrtcIsReady() {
+    return null !== this.client && 'open' === this.client.readyState;
+  }
 
-  };
+  relayIsReady() {
+    return (null !== this.relay) && (WebSocket.OPEN === this.relay.readyState);
+  }
 
-  ProxyPair.prototype.MAX_BUFFER = 10 * 1024 * 1024;
+  isClosed(ws) {
+    return void 0 === ws || WebSocket.CLOSED === ws.readyState;
+  }
 
-  ProxyPair.prototype.pc = null;
+};
 
-  ProxyPair.prototype.client = null; // WebRTC Data channel
+ProxyPair.prototype.MAX_BUFFER = 10 * 1024 * 1024;
 
-  ProxyPair.prototype.relay = null; // websocket
+ProxyPair.prototype.pc = null;
 
-  ProxyPair.prototype.timer = 0;
+ProxyPair.prototype.client = null; // WebRTC Data channel
 
-  ProxyPair.prototype.running = true;
+ProxyPair.prototype.relay = null; // websocket
 
-  ProxyPair.prototype.active = false; // Whether serving a client.
+ProxyPair.prototype.timer = 0;
 
-  ProxyPair.prototype.flush_timeout_id = null;
+ProxyPair.prototype.running = true;
 
-  ProxyPair.prototype.onCleanup = null;
+ProxyPair.prototype.active = false; // Whether serving a client.
 
-  ProxyPair.prototype.id = null;
+ProxyPair.prototype.flush_timeout_id = null;
 
-  return ProxyPair;
+ProxyPair.prototype.onCleanup = null;
 
-}).call(this);
+ProxyPair.prototype.id = null;
diff --git a/proxy/shims.js b/proxy/shims.js
index 0e33236..4fc0b38 100644
--- a/proxy/shims.js
+++ b/proxy/shims.js
@@ -1,8 +1,6 @@
-// Generated by CoffeeScript 2.4.1
 /*
 WebRTC shims for multiple browsers.
 */
-var IceCandidate, PeerConnection, SessionDescription, WebSocket, XMLHttpRequest, chrome, document, location, webrtc, window;
 
 if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
   window = {};
@@ -19,10 +17,9 @@ if (typeof module !== "undefined" && module !== null ? module.exports : void 0)
     IceCandidate = webrtc.RTCIceCandidate;
     SessionDescription = webrtc.RTCSessionDescription;
     WebSocket = require('ws');
-    ({XMLHttpRequest} = require('xmlhttprequest'));
+    ({ XMLHttpRequest } = require('xmlhttprequest'));
   }
 } else {
-  window = this;
   document = window.document;
   chrome = window.chrome;
   location = window.location.search.substr(1);
diff --git a/proxy/snowflake.js b/proxy/snowflake.js
index c150452..7b584d1 100644
--- a/proxy/snowflake.js
+++ b/proxy/snowflake.js
@@ -1,4 +1,3 @@
-// Generated by CoffeeScript 2.4.1
 /*
 A Coffeescript WebRTC snowflake proxy
 
@@ -9,174 +8,169 @@ this proxy must always act as the answerer.
 
 TODO: More documentation
 */
-var Snowflake;
-
-Snowflake = (function() {
-  // Minimum viable snowflake for now - just 1 client.
-  class Snowflake {
-    // Prepare the Snowflake with a Broker (to find clients) and optional UI.
-    constructor(config, ui, broker) {
-      // Receive an SDP offer from some client assigned by the Broker,
-      // |pair| - an available ProxyPair.
-      this.receiveOffer = this.receiveOffer.bind(this);
-      this.config = config;
-      this.ui = ui;
-      this.broker = broker;
-      this.state = Snowflake.MODE.INIT;
-      this.proxyPairs = [];
-      if (void 0 === this.config.rateLimitBytes) {
-        this.rateLimit = new DummyRateLimit();
-      } else {
-        this.rateLimit = new BucketRateLimit(this.config.rateLimitBytes * this.config.rateLimitHistory, this.config.rateLimitHistory);
-      }
-      this.retries = 0;
-    }
 
-    // Set the target relay address spec, which is expected to be websocket.
-    // TODO: Should potentially fetch the target from broker later, or modify
-    // entirely for the Tor-independent version.
-    setRelayAddr(relayAddr) {
-      this.relayAddr = relayAddr;
-      log('Using ' + relayAddr.host + ':' + relayAddr.port + ' as Relay.');
-      return true;
+// Minimum viable snowflake for now - just 1 client.
+class Snowflake {
+
+  // Prepare the Snowflake with a Broker (to find clients) and optional UI.
+  constructor(config, ui, broker) {
+    // Receive an SDP offer from some client assigned by the Broker,
+    // |pair| - an available ProxyPair.
+    this.receiveOffer = this.receiveOffer.bind(this);
+    this.config = config;
+    this.ui = ui;
+    this.broker = broker;
+    this.state = Snowflake.MODE.INIT;
+    this.proxyPairs = [];
+    if (void 0 === this.config.rateLimitBytes) {
+      this.rateLimit = new DummyRateLimit();
+    } else {
+      this.rateLimit = new BucketRateLimit(this.config.rateLimitBytes * this.config.rateLimitHistory, this.config.rateLimitHistory);
     }
-
-    // Initialize WebRTC PeerConnection, which requires beginning the signalling
-    // process. |pollBroker| automatically arranges signalling.
-    beginWebRTC() {
-      this.state = Snowflake.MODE.WEBRTC_CONNECTING;
-      log('ProxyPair Slots: ' + this.proxyPairs.length);
-      log('Snowflake IDs: ' + (this.proxyPairs.map(function(p) {
-        return p.id;
-      })).join(' | '));
-      this.pollBroker();
-      return this.pollInterval = setInterval((() => {
-        return this.pollBroker();
-      }), this.config.defaultBrokerPollInterval);
+    this.retries = 0;
+  }
+
+  // Set the target relay address spec, which is expected to be websocket.
+  // TODO: Should potentially fetch the target from broker later, or modify
+  // entirely for the Tor-independent version.
+  setRelayAddr(relayAddr) {
+    this.relayAddr = relayAddr;
+    log('Using ' + relayAddr.host + ':' + relayAddr.port + ' as Relay.');
+    return true;
+  }
+
+  // Initialize WebRTC PeerConnection, which requires beginning the signalling
+  // process. |pollBroker| automatically arranges signalling.
+  beginWebRTC() {
+    this.state = Snowflake.MODE.WEBRTC_CONNECTING;
+    log('ProxyPair Slots: ' + this.proxyPairs.length);
+    log('Snowflake IDs: ' + (this.proxyPairs.map(function(p) {
+      return p.id;
+    })).join(' | '));
+    this.pollBroker();
+    return this.pollInterval = setInterval((() => {
+      return this.pollBroker();
+    }), this.config.defaultBrokerPollInterval);
+  }
+
+  // Regularly poll Broker for clients to serve until this snowflake is
+  // serving at capacity, at which point stop polling.
+  pollBroker() {
+    var msg, pair, recv;
+    // Poll broker for clients.
+    pair = this.nextAvailableProxyPair();
+    if (!pair) {
+      log('At client capacity.');
+      return;
     }
-
-    // Regularly poll Broker for clients to serve until this snowflake is
-    // serving at capacity, at which point stop polling.
-    pollBroker() {
-      var msg, pair, recv;
-      // Poll broker for clients.
-      pair = this.nextAvailableProxyPair();
-      if (!pair) {
-        log('At client capacity.');
-        return;
-      }
-      // Do nothing until a new proxyPair is available.
-      pair.active = true;
-      msg = 'Polling for client ... ';
-      if (this.retries > 0) {
-        msg += '[retries: ' + this.retries + ']';
-      }
-      this.ui.setStatus(msg);
-      recv = this.broker.getClientOffer(pair.id);
-      recv.then((desc) => {
-        if (pair.running) {
-          if (!this.receiveOffer(pair, desc)) {
-            return pair.active = false;
-          }
-        } else {
+    // Do nothing until a new proxyPair is available.
+    pair.active = true;
+    msg = 'Polling for client ... ';
+    if (this.retries > 0) {
+      msg += '[retries: ' + this.retries + ']';
+    }
+    this.ui.setStatus(msg);
+    recv = this.broker.getClientOffer(pair.id);
+    recv.then((desc) => {
+      if (pair.running) {
+        if (!this.receiveOffer(pair, desc)) {
           return pair.active = false;
         }
-      }, function(err) {
+      } else {
         return pair.active = false;
-      });
-      return this.retries++;
-    }
-
-    // Returns the first ProxyPair that's available to connect.
-    nextAvailableProxyPair() {
-      if (this.proxyPairs.length < this.config.connectionsPerClient) {
-        return this.makeProxyPair(this.relayAddr);
       }
-      return this.proxyPairs.find(function(pp, i, arr) {
-        return !pp.active;
-      });
+    }, function(err) {
+      return pair.active = false;
+    });
+    return this.retries++;
+  }
+
+  // Returns the first ProxyPair that's available to connect.
+  nextAvailableProxyPair() {
+    if (this.proxyPairs.length < this.config.connectionsPerClient) {
+      return this.makeProxyPair(this.relayAddr);
     }
-
-    receiveOffer(pair, desc) {
-      var e, offer, sdp;
-      try {
-        offer = JSON.parse(desc);
-        dbg('Received:\n\n' + offer.sdp + '\n');
-        sdp = new SessionDescription(offer);
-        if (pair.receiveWebRTCOffer(sdp)) {
-          this.sendAnswer(pair);
-          return true;
-        } else {
-          return false;
-        }
-      } catch (error) {
-        e = error;
-        log('ERROR: Unable to receive Offer: ' + e);
+    return this.proxyPairs.find(function(pp, i, arr) {
+      return !pp.active;
+    });
+  }
+
+  receiveOffer(pair, desc) {
+    var e, offer, sdp;
+    try {
+      offer = JSON.parse(desc);
+      dbg('Received:\n\n' + offer.sdp + '\n');
+      sdp = new SessionDescription(offer);
+      if (pair.receiveWebRTCOffer(sdp)) {
+        this.sendAnswer(pair);
+        return true;
+      } else {
         return false;
       }
+    } catch (error) {
+      e = error;
+      log('ERROR: Unable to receive Offer: ' + e);
+      return false;
     }
-
-    sendAnswer(pair) {
-      var fail, next;
-      next = function(sdp) {
-        dbg('webrtc: Answer ready.');
-        return pair.pc.setLocalDescription(sdp);
-      };
-      fail = function() {
-        return dbg('webrtc: Failed to create Answer');
-      };
-      return pair.pc.createAnswer().then(next).catch(fail);
-    }
-
-    makeProxyPair(relay) {
-      var pair;
-      pair = new ProxyPair(relay, this.rateLimit, this.config.pcConfig);
-      this.proxyPairs.push(pair);
-      pair.onCleanup = (event) => {
-        var ind;
-        // Delete from the list of active proxy pairs.
-        ind = this.proxyPairs.indexOf(pair);
-        if (ind > -1) {
-          return this.proxyPairs.splice(ind, 1);
-        }
-      };
-      pair.begin();
-      return pair;
-    }
-
-    // Stop all proxypairs.
-    disable() {
-      var results;
-      log('Disabling Snowflake.');
-      clearInterval(this.pollInterval);
-      results = [];
-      while (this.proxyPairs.length > 0) {
-        results.push(this.proxyPairs.pop().close());
+  }
+
+  sendAnswer(pair) {
+    var fail, next;
+    next = function(sdp) {
+      dbg('webrtc: Answer ready.');
+      return pair.pc.setLocalDescription(sdp);
+    };
+    fail = function() {
+      return dbg('webrtc: Failed to create Answer');
+    };
+    return pair.pc.createAnswer().then(next).catch(fail);
+  }
+
+  makeProxyPair(relay) {
+    var pair;
+    pair = new ProxyPair(relay, this.rateLimit, this.config.pcConfig);
+    this.proxyPairs.push(pair);
+    pair.onCleanup = (event) => {
+      var ind;
+      // Delete from the list of active proxy pairs.
+      ind = this.proxyPairs.indexOf(pair);
+      if (ind > -1) {
+        return this.proxyPairs.splice(ind, 1);
       }
-      return results;
+    };
+    pair.begin();
+    return pair;
+  }
+
+  // Stop all proxypairs.
+  disable() {
+    var results;
+    log('Disabling Snowflake.');
+    clearInterval(this.pollInterval);
+    results = [];
+    while (this.proxyPairs.length > 0) {
+      results.push(this.proxyPairs.pop().close());
     }
+    return results;
+  }
 
-  };
-
-  Snowflake.prototype.relayAddr = null;
-
-  Snowflake.prototype.rateLimit = null;
+};
 
-  Snowflake.prototype.pollInterval = null;
+Snowflake.prototype.relayAddr = null;
 
-  Snowflake.prototype.retries = 0;
+Snowflake.prototype.rateLimit = null;
 
-  // Janky state machine
-  Snowflake.MODE = {
-    INIT: 0,
-    WEBRTC_CONNECTING: 1,
-    WEBRTC_READY: 2
-  };
+Snowflake.prototype.pollInterval = null;
 
-  Snowflake.MESSAGE = {
-    CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
-  };
+Snowflake.prototype.retries = 0;
 
-  return Snowflake;
+// Janky state machine
+Snowflake.MODE = {
+  INIT: 0,
+  WEBRTC_CONNECTING: 1,
+  WEBRTC_READY: 2
+};
 
-}).call(this);
+Snowflake.MESSAGE = {
+  CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
+};
diff --git a/proxy/spec/broker.spec.js b/proxy/spec/broker.spec.js
index 7d5e0a2..a7c2e1d 100644
--- a/proxy/spec/broker.spec.js
+++ b/proxy/spec/broker.spec.js
@@ -1,39 +1,32 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake broker
 */
-var XMLHttpRequest;
 
-XMLHttpRequest = (function() {
-  // fake xhr
-  // class XMLHttpRequest
-  class XMLHttpRequest {
-    constructor() {
-      this.onreadystatechange = null;
-    }
+// fake xhr
+// class XMLHttpRequest
+class XMLHttpRequest {
+  constructor() {
+    this.onreadystatechange = null;
+  }
+  open() {}
+  setRequestHeader() {}
+  send() {}
+};
 
-    open() {}
+XMLHttpRequest.prototype.DONE = 1;
 
-    setRequestHeader() {}
-
-    send() {}
-
-  };
-
-  XMLHttpRequest.prototype.DONE = 1;
-
-  return XMLHttpRequest;
-
-}).call(this);
 
 describe('Broker', function() {
+
   it('can be created', function() {
     var b;
     b = new Broker('fake');
     expect(b.url).toEqual('https://fake/');
-    return expect(b.id).not.toBeNull();
+    expect(b.id).not.toBeNull();
   });
+
   describe('getClientOffer', function() {
+
     it('polls and promises a client offer', function(done) {
       var b, poll;
       b = new Broker('fake');
@@ -55,6 +48,7 @@ describe('Broker', function() {
         return done();
       });
     });
+
     it('rejects if the broker timed-out', function(done) {
       var b, poll;
       b = new Broker('fake');
@@ -75,7 +69,8 @@ describe('Broker', function() {
         return done();
       });
     });
-    return it('rejects on any other status', function(done) {
+
+    it('rejects on any other status', function(done) {
       var b, poll;
       b = new Broker('fake');
       // fake timed-out request from broker
@@ -95,18 +90,20 @@ describe('Broker', function() {
         expect(b._xhr.status).toBe(1337);
         return done();
       });
+
     });
+
   });
+
   it('responds to the broker with answer', function() {
-    var b;
-    b = new Broker('fake');
+    var b = new Broker('fake');
     spyOn(b, '_postRequest');
     b.sendAnswer('fake id', 123);
-    return expect(b._postRequest).toHaveBeenCalledWith('fake id', jasmine.any(Object), 'answer', '123');
+    expect(b._postRequest).toHaveBeenCalledWith('fake id', jasmine.any(Object), 'answer', '123');
   });
-  return it('POST XMLHttpRequests to the broker', function() {
-    var b;
-    b = new Broker('fake');
+
+  it('POST XMLHttpRequests to the broker', function() {
+    var b = new Broker('fake');
     b._xhr = new XMLHttpRequest();
     spyOn(b._xhr, 'open');
     spyOn(b._xhr, 'setRequestHeader');
@@ -114,6 +111,7 @@ describe('Broker', function() {
     b._postRequest(0, b._xhr, 'test', 'data');
     expect(b._xhr.open).toHaveBeenCalled();
     expect(b._xhr.setRequestHeader).toHaveBeenCalled();
-    return expect(b._xhr.send).toHaveBeenCalled();
+    expect(b._xhr.send).toHaveBeenCalled();
   });
+
 });
diff --git a/proxy/spec/init.spec.js b/proxy/spec/init.spec.js
index 70ec7e9..baa7388 100644
--- a/proxy/spec/init.spec.js
+++ b/proxy/spec/init.spec.js
@@ -1,8 +1,6 @@
-// Generated by CoffeeScript 2.4.1
 // Fake snowflake to interact with
-var snowflake;
 
-snowflake = {
+var snowflake = {
   ui: new UI,
   broker: {
     sendAnswer: function() {}
@@ -11,24 +9,25 @@ snowflake = {
 };
 
 describe('Init', function() {
+
   it('gives a dialog when closing, only while active', function() {
-    var msg, silenceNotifications;
     silenceNotifications = false;
     snowflake.state = Snowflake.MODE.WEBRTC_READY;
-    msg = window.onbeforeunload();
+    var msg = window.onbeforeunload();
     expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
     expect(msg).toBe(Snowflake.MESSAGE.CONFIRMATION);
     snowflake.state = Snowflake.MODE.INIT;
     msg = window.onbeforeunload();
     expect(snowflake.state).toBe(Snowflake.MODE.INIT);
-    return expect(msg).toBe(null);
+    expect(msg).toBe(null);
   });
-  return it('does not give a dialog when silent flag is on', function() {
-    var msg, silenceNotifications;
+
+  it('does not give a dialog when silent flag is on', function() {
     silenceNotifications = true;
     snowflake.state = Snowflake.MODE.WEBRTC_READY;
-    msg = window.onbeforeunload();
+    var msg = window.onbeforeunload();
     expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
-    return expect(msg).toBe(null);
+    expect(msg).toBe(null);
   });
+
 });
diff --git a/proxy/spec/proxypair.spec.js b/proxy/spec/proxypair.spec.js
index af6a5a6..59ad85a 100644
--- a/proxy/spec/proxypair.spec.js
+++ b/proxy/spec/proxypair.spec.js
@@ -1,17 +1,15 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake proxypair
 */
-var MessageEvent, arrayMatching;
 
 // Replacement for MessageEvent constructor.
 // https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/MessageEvent
-MessageEvent = function(type, init) {
+var MessageEvent = function(type, init) {
   return init;
 };
 
 // Asymmetic matcher that checks that two arrays have the same contents.
-arrayMatching = function(sample) {
+var arrayMatching = function(sample) {
   return {
     asymmetricMatch: function(other) {
       var _, a, b, i, j, len;
@@ -35,46 +33,57 @@ arrayMatching = function(sample) {
 };
 
 describe('ProxyPair', function() {
+
   var config, destination, fakeRelay, pp, rateLimit;
   fakeRelay = Parse.address('0.0.0.0:12345');
   rateLimit = new DummyRateLimit;
   config = new Config;
   destination = [];
+
   // Using the mock PeerConnection definition from spec/snowflake.spec.coffee.
-  pp = new ProxyPair(fakeRelay, rateLimit, config.pcConfig);
+  var pp = new ProxyPair(fakeRelay, rateLimit, config.pcConfig);
+
   beforeEach(function() {
     return pp.begin();
   });
+
   it('begins webrtc connection', function() {
     return expect(pp.pc).not.toBeNull();
   });
+
   describe('accepts WebRTC offer from some client', function() {
+
     beforeEach(function() {
       return pp.begin();
     });
+
     it('rejects invalid offers', function() {
       expect(typeof pp.pc.setRemoteDescription).toBe("function");
       expect(pp.pc).not.toBeNull();
       expect(pp.receiveWebRTCOffer({})).toBe(false);
-      return expect(pp.receiveWebRTCOffer({
+      expect(pp.receiveWebRTCOffer({
         type: 'answer'
       })).toBe(false);
     });
-    return it('accepts valid offers', function() {
+
+    it('accepts valid offers', function() {
       expect(pp.pc).not.toBeNull();
-      return expect(pp.receiveWebRTCOffer({
+      expect(pp.receiveWebRTCOffer({
         type: 'offer',
         sdp: 'foo'
       })).toBe(true);
     });
+
   });
+
   it('responds with a WebRTC answer correctly', function() {
     spyOn(snowflake.broker, 'sendAnswer');
     pp.pc.onicecandidate({
       candidate: null
     });
-    return expect(snowflake.broker.sendAnswer).toHaveBeenCalled();
+    expect(snowflake.broker.sendAnswer).toHaveBeenCalled();
   });
+
   it('handles a new data channel correctly', function() {
     expect(pp.client).toBeNull();
     pp.pc.ondatachannel({
@@ -84,21 +93,25 @@ describe('ProxyPair', function() {
     expect(pp.client.onopen).not.toBeNull();
     expect(pp.client.onclose).not.toBeNull();
     expect(pp.client.onerror).not.toBeNull();
-    return expect(pp.client.onmessage).not.toBeNull();
+    expect(pp.client.onmessage).not.toBeNull();
   });
+
   it('connects to the relay once datachannel opens', function() {
     spyOn(pp, 'connectRelay');
     pp.client.onopen();
-    return expect(pp.connectRelay).toHaveBeenCalled();
+    expect(pp.connectRelay).toHaveBeenCalled();
   });
+
   it('connects to a relay', function() {
     pp.connectRelay();
     expect(pp.relay.onopen).not.toBeNull();
     expect(pp.relay.onclose).not.toBeNull();
     expect(pp.relay.onerror).not.toBeNull();
-    return expect(pp.relay.onmessage).not.toBeNull();
+    expect(pp.relay.onmessage).not.toBeNull();
   });
-  return describe('flushes data between client and relay', function() {
+
+  describe('flushes data between client and relay', function() {
+
     it('proxies data from client to relay', function() {
       var msg;
       pp.pc.ondatachannel({
@@ -116,8 +129,9 @@ describe('ProxyPair', function() {
       pp.onClientToRelayMessage(msg);
       pp.flush();
       expect(pp.client.send).not.toHaveBeenCalled();
-      return expect(pp.relay.send).toHaveBeenCalledWith(arrayMatching([1, 2, 3]));
+      expect(pp.relay.send).toHaveBeenCalledWith(arrayMatching([1, 2, 3]));
     });
+
     it('proxies data from relay to client', function() {
       var msg;
       spyOn(pp.client, 'send');
@@ -128,16 +142,19 @@ describe('ProxyPair', function() {
       pp.onRelayToClientMessage(msg);
       pp.flush();
       expect(pp.client.send).toHaveBeenCalledWith(arrayMatching([4, 5, 6]));
-      return expect(pp.relay.send).not.toHaveBeenCalled();
+      expect(pp.relay.send).not.toHaveBeenCalled();
     });
-    return it('sends nothing with nothing to flush', function() {
+
+    it('sends nothing with nothing to flush', function() {
       spyOn(pp.client, 'send');
       spyOn(pp.relay, 'send');
       pp.flush();
       expect(pp.client.send).not.toHaveBeenCalled();
-      return expect(pp.relay.send).not.toHaveBeenCalled();
+      expect(pp.relay.send).not.toHaveBeenCalled();
     });
+
   });
+
 });
 
 // TODO: rate limit tests
diff --git a/proxy/spec/snowflake.spec.js b/proxy/spec/snowflake.spec.js
index d3fb988..eccc24d 100644
--- a/proxy/spec/snowflake.spec.js
+++ b/proxy/spec/snowflake.spec.js
@@ -1,62 +1,43 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake
 */
-var FakeBroker, PeerConnection, SessionDescription, WebSocket, config, log, ui;
 
 // Fake browser functionality:
-PeerConnection = class PeerConnection {
+class PeerConnection {
   setRemoteDescription() {
     return true;
   }
-
   send(data) {}
-
 };
 
-SessionDescription = (function() {
-  class SessionDescription {};
-
-  SessionDescription.prototype.type = 'offer';
-
-  return SessionDescription;
-
-}).call(this);
-
-WebSocket = (function() {
-  class WebSocket {
-    constructor() {
-      this.bufferedAmount = 0;
-    }
-
-    send(data) {}
-
-  };
-
-  WebSocket.prototype.OPEN = 1;
-
-  WebSocket.prototype.CLOSED = 0;
-
-  return WebSocket;
+class SessionDescription {};
+SessionDescription.prototype.type = 'offer';
 
-}).call(this);
+class WebSocket {
+  constructor() {
+    this.bufferedAmount = 0;
+  }
+  send(data) {}
+};
+WebSocket.prototype.OPEN = 1;
+WebSocket.prototype.CLOSED = 0;
 
-log = function() {};
+var log = function() {};
 
-config = new Config;
+var config = new Config;
 
-ui = new UI;
+var ui = new UI;
 
-FakeBroker = class FakeBroker {
+class FakeBroker {
   getClientOffer() {
     return new Promise(function(F, R) {
       return {};
     });
   }
-
 };
 
 describe('Snowflake', function() {
+
   it('constructs correctly', function() {
     var s;
     s = new Snowflake(config, ui, {
@@ -67,22 +48,25 @@ describe('Snowflake', function() {
       fake: 'broker'
     });
     expect(s.ui).not.toBeNull();
-    return expect(s.retries).toBe(0);
+    expect(s.retries).toBe(0);
   });
+
   it('sets relay address correctly', function() {
     var s;
     s = new Snowflake(config, ui, null);
     s.setRelayAddr('foo');
-    return expect(s.relayAddr).toEqual('foo');
+    expect(s.relayAddr).toEqual('foo');
   });
+
   it('initalizes WebRTC connection', function() {
     var s;
     s = new Snowflake(config, ui, new FakeBroker());
     spyOn(s.broker, 'getClientOffer').and.callThrough();
     s.beginWebRTC();
     expect(s.retries).toBe(1);
-    return expect(s.broker.getClientOffer).toHaveBeenCalled();
+    expect(s.broker.getClientOffer).toHaveBeenCalled();
   });
+
   it('receives SDP offer and sends answer', function() {
     var pair, s;
     s = new Snowflake(config, ui, new FakeBroker());
@@ -92,8 +76,9 @@ describe('Snowflake', function() {
     spyOn(pair, 'receiveWebRTCOffer').and.returnValue(true);
     spyOn(s, 'sendAnswer');
     s.receiveOffer(pair, '{"type":"offer","sdp":"foo"}');
-    return expect(s.sendAnswer).toHaveBeenCalled();
+    expect(s.sendAnswer).toHaveBeenCalled();
   });
+
   it('does not send answer when receiving invalid offer', function() {
     var pair, s;
     s = new Snowflake(config, ui, new FakeBroker());
@@ -103,12 +88,14 @@ describe('Snowflake', function() {
     spyOn(pair, 'receiveWebRTCOffer').and.returnValue(false);
     spyOn(s, 'sendAnswer');
     s.receiveOffer(pair, '{"type":"not a good offer","sdp":"foo"}');
-    return expect(s.sendAnswer).not.toHaveBeenCalled();
+    expect(s.sendAnswer).not.toHaveBeenCalled();
   });
-  return it('can make a proxypair', function() {
+
+  it('can make a proxypair', function() {
     var s;
     s = new Snowflake(config, ui, new FakeBroker());
     s.makeProxyPair();
-    return expect(s.proxyPairs.length).toBe(1);
+    expect(s.proxyPairs.length).toBe(1);
   });
+
 });
diff --git a/proxy/spec/ui.spec.js b/proxy/spec/ui.spec.js
index 380f41a..8ae141f 100644
--- a/proxy/spec/ui.spec.js
+++ b/proxy/spec/ui.spec.js
@@ -1,10 +1,7 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake UI
 */
-var document;
-
-document = {
+var document = {
   getElementById: function(id) {
     return {};
   },
@@ -14,6 +11,7 @@ document = {
 };
 
 describe('UI', function() {
+
   it('activates debug mode when badge does not exist', function() {
     var u;
     spyOn(document, 'getElementById').and.callFake(function(id) {
@@ -25,8 +23,9 @@ describe('UI', function() {
     u = new DebugUI();
     expect(document.getElementById.calls.count()).toEqual(2);
     expect(u.$status).not.toBeNull();
-    return expect(u.$msglog).not.toBeNull();
+    expect(u.$msglog).not.toBeNull();
   });
+
   it('is not debug mode when badge exists', function() {
     var u;
     spyOn(document, 'getElementById').and.callFake(function(id) {
@@ -38,8 +37,9 @@ describe('UI', function() {
     u = new BadgeUI();
     expect(document.getElementById).toHaveBeenCalled();
     expect(document.getElementById.calls.count()).toEqual(1);
-    return expect(u.$badge).not.toBeNull();
+    expect(u.$badge).not.toBeNull();
   });
+
   it('sets status message when in debug mode', function() {
     var u;
     u = new DebugUI();
@@ -50,16 +50,18 @@ describe('UI', function() {
       }
     };
     u.setStatus('test');
-    return expect(u.$status.innerHTML).toEqual('Status: test');
+    expect(u.$status.innerHTML).toEqual('Status: test');
   });
+
   it('sets message log css correctly for debug mode', function() {
     var u;
     u = new DebugUI();
     u.setActive(true);
     expect(u.$msglog.className).toEqual('active');
     u.setActive(false);
-    return expect(u.$msglog.className).toEqual('');
+    expect(u.$msglog.className).toEqual('');
   });
+
   it('sets badge css correctly for non-debug mode', function() {
     var u;
     u = new BadgeUI();
@@ -67,9 +69,10 @@ describe('UI', function() {
     u.setActive(true);
     expect(u.$badge.className).toEqual('active');
     u.setActive(false);
-    return expect(u.$badge.className).toEqual('');
+    expect(u.$badge.className).toEqual('');
   });
-  return it('logs to the textarea correctly when debug mode', function() {
+
+  it('logs to the textarea correctly when debug mode', function() {
     var u;
     u = new DebugUI();
     u.$msglog = {
@@ -79,6 +82,7 @@ describe('UI', function() {
     };
     u.log('test');
     expect(u.$msglog.value).toEqual('test\n');
-    return expect(u.$msglog.scrollTop).toEqual(1337);
+    expect(u.$msglog.scrollTop).toEqual(1337);
   });
+
 });
diff --git a/proxy/spec/util.spec.js b/proxy/spec/util.spec.js
index 65f2324..c885b25 100644
--- a/proxy/spec/util.spec.js
+++ b/proxy/spec/util.spec.js
@@ -1,10 +1,12 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake utils
 */
+
 describe('Parse', function() {
+
   describe('cookie', function() {
-    return it('parses correctly', function() {
+
+    it('parses correctly', function() {
       expect(Parse.cookie('')).toEqual({});
       expect(Parse.cookie('a=b')).toEqual({
         a: 'b'
@@ -30,12 +32,15 @@ describe('Parse', function() {
       expect(Parse.cookie('key=%26%20')).toEqual({
         key: '& '
       });
-      return expect(Parse.cookie('a=\'\'')).toEqual({
+      expect(Parse.cookie('a=\'\'')).toEqual({
         a: '\'\''
       });
     });
+
   });
+
   describe('address', function() {
+
     it('parses IPv4', function() {
       expect(Parse.address('')).toBeNull();
       expect(Parse.address('3.3.3.3:4444')).toEqual({
@@ -45,9 +50,10 @@ describe('Parse', function() {
       expect(Parse.address('3.3.3.3')).toBeNull();
       expect(Parse.address('3.3.3.3:0x1111')).toBeNull();
       expect(Parse.address('3.3.3.3:-4444')).toBeNull();
-      return expect(Parse.address('3.3.3.3:65536')).toBeNull();
+      expect(Parse.address('3.3.3.3:65536')).toBeNull();
     });
-    return it('parses IPv6', function() {
+
+    it('parses IPv6', function() {
       expect(Parse.address('[1:2::a:f]:4444')).toEqual({
         host: '1:2::a:f',
         port: 4444
@@ -56,15 +62,17 @@ describe('Parse', function() {
       expect(Parse.address('[1:2::a:f]:0x1111')).toBeNull();
       expect(Parse.address('[1:2::a:f]:-4444')).toBeNull();
       expect(Parse.address('[1:2::a:f]:65536')).toBeNull();
-      return expect(Parse.address('[1:2::ffff:1.2.3.4]:4444')).toEqual({
+      expect(Parse.address('[1:2::ffff:1.2.3.4]:4444')).toEqual({
         host: '1:2::ffff:1.2.3.4',
         port: 4444
       });
     });
+
   });
-  return describe('ipFromSDP', function() {
-    var testCases;
-    testCases = [
+
+  describe('ipFromSDP', function() {
+
+    var testCases = [
       {
         // https://tools.ietf.org/html/rfc4566#section-5
         sdp: "v=0\no=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\ns=SDP Seminar\ni=A Seminar on the session description protocol\nu=http://www.example.com/seminars/sdp.pdf\ne=j.doe@xxxxxxxxxxx (Jane Doe)\nc=IN IP4 224.2.17.12/127\nt=2873397496 2873404696\na=recvonly\nm=audio 49170 RTP/AVP 0\nm=video 51372 RTP/AVP 99\na=rtpmap:99 h263-1998/90000",
@@ -128,7 +136,8 @@ describe('Parse', function() {
         expected: void 0
       }
     ];
-    return it('parses SDP', function() {
+
+    it('parses SDP', function() {
       var i, len, ref, ref1, results, test;
       results = [];
       for (i = 0, len = testCases.length; i < len; i++) {
@@ -143,10 +152,13 @@ describe('Parse', function() {
       }
       return results;
     });
+
   });
+
 });
 
 describe('query string', function() {
+
   it('should parse correctly', function() {
     expect(Query.parse('')).toEqual({});
     expect(Query.parse('a=b')).toEqual({
@@ -178,11 +190,12 @@ describe('query string', function() {
     expect(Query.parse('a+b=c')).toEqual({
       'a b': 'c'
     });
-    return expect(Query.parse('a=b+c+d')).toEqual({
+    expect(Query.parse('a=b+c+d')).toEqual({
       a: 'b c d'
     });
   });
-  return it('uses the first appearance of duplicate key', function() {
+
+  it('uses the first appearance of duplicate key', function() {
     expect(Query.parse('a=b&c=d&a=e')).toEqual({
       a: 'b',
       c: 'd'
@@ -201,21 +214,24 @@ describe('query string', function() {
       a: 'b',
       '': ''
     });
-    return expect(Query.parse('a=b&&c=d')).toEqual({
+    expect(Query.parse('a=b&&c=d')).toEqual({
       a: 'b',
       '': '',
       c: 'd'
     });
   });
+
 });
 
 describe('Params', function() {
+
   describe('bool', function() {
-    var getBool;
-    getBool = function(query) {
+
+    var getBool = function(query) {
       return Params.getBool(Query.parse(query), 'param', false);
     };
-    return it('parses correctly', function() {
+
+    it('parses correctly', function() {
       expect(getBool('param=true')).toBe(true);
       expect(getBool('param')).toBe(true);
       expect(getBool('param=')).toBe(true);
@@ -223,19 +239,23 @@ describe('Params', function() {
       expect(getBool('param=0')).toBe(false);
       expect(getBool('param=false')).toBe(false);
       expect(getBool('param=unexpected')).toBeNull();
-      return expect(getBool('pram=true')).toBe(false);
+      expect(getBool('pram=true')).toBe(false);
     });
+
   });
-  return describe('address', function() {
-    var DEFAULT, getAddress;
-    DEFAULT = {
+
+  describe('address', function() {
+
+    var DEFAULT = {
       host: '1.1.1.1',
       port: 2222
     };
-    getAddress = function(query) {
+
+    var getAddress = function(query) {
       return Params.getAddress(query, 'addr', DEFAULT);
     };
-    return it('parses correctly', function() {
+
+    it('parses correctly', function() {
       expect(getAddress({})).toEqual(DEFAULT);
       expect(getAddress({
         addr: '3.3.3.3:4444'
@@ -246,9 +266,11 @@ describe('Params', function() {
       expect(getAddress({
         x: '3.3.3.3:4444'
       })).toEqual(DEFAULT);
-      return expect(getAddress({
+      expect(getAddress({
         addr: '---'
       })).toBeNull();
     });
+
   });
+
 });
diff --git a/proxy/spec/websocket.spec.js b/proxy/spec/websocket.spec.js
index 41314df..370995a 100644
--- a/proxy/spec/websocket.spec.js
+++ b/proxy/spec/websocket.spec.js
@@ -1,32 +1,39 @@
-// Generated by CoffeeScript 2.4.1
 /*
 jasmine tests for Snowflake websocket
 */
+
 describe('BuildUrl', function() {
+
   it('should parse just protocol and host', function() {
-    return expect(WS.buildUrl('http', 'example.com')).toBe('http://example.com');
+    expect(WS.buildUrl('http', 'example.com')).toBe('http://example.com');
   });
+
   it('should handle different ports', function() {
     expect(WS.buildUrl('http', 'example.com', 80)).toBe('http://example.com');
     expect(WS.buildUrl('http', 'example.com', 81)).toBe('http://example.com:81');
     expect(WS.buildUrl('http', 'example.com', 443)).toBe('http://example.com:443');
-    return expect(WS.buildUrl('http', 'example.com', 444)).toBe('http://example.com:444');
+    expect(WS.buildUrl('http', 'example.com', 444)).toBe('http://example.com:444');
   });
+
   it('should handle paths', function() {
     expect(WS.buildUrl('http', 'example.com', 80, '/')).toBe('http://example.com/');
     expect(WS.buildUrl('http', 'example.com', 80, '/test?k=%#v')).toBe('http://example.com/test%3Fk%3D%25%23v');
-    return expect(WS.buildUrl('http', 'example.com', 80, '/test')).toBe('http://example.com/test');
+    expect(WS.buildUrl('http', 'example.com', 80, '/test')).toBe('http://example.com/test');
   });
+
   it('should handle params', function() {
     expect(WS.buildUrl('http', 'example.com', 80, '/test', [['k', '%#v']])).toBe('http://example.com/test?k=%25%23v');
-    return expect(WS.buildUrl('http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']])).toBe('http://example.com/test?a=b&c=d');
+    expect(WS.buildUrl('http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']])).toBe('http://example.com/test?a=b&c=d');
   });
+
   it('should handle ips', function() {
     expect(WS.buildUrl('http', '1.2.3.4')).toBe('http://1.2.3.4');
-    return expect(WS.buildUrl('http', '1:2::3:4')).toBe('http://[1:2::3:4]');
+    expect(WS.buildUrl('http', '1:2::3:4')).toBe('http://[1:2::3:4]');
   });
-  return it('should handle bogus', function() {
+
+  it('should handle bogus', function() {
     expect(WS.buildUrl('http', 'bog][us')).toBe('http://bog%5D%5Bus');
-    return expect(WS.buildUrl('http', 'bog:u]s')).toBe('http://bog%3Au%5Ds');
+    expect(WS.buildUrl('http', 'bog:u]s')).toBe('http://bog%3Au%5Ds');
   });
+
 });
diff --git a/proxy/ui.js b/proxy/ui.js
index b3f0ae1..da14800 100644
--- a/proxy/ui.js
+++ b/proxy/ui.js
@@ -1,197 +1,178 @@
-// Generated by CoffeeScript 2.4.1
-  /*
-  All of Snowflake's DOM manipulation and inputs.
-  */
-var BadgeUI, DebugUI, UI, WebExtUI,
-  boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
-
-UI = (function() {
-  class UI {
-    setStatus(msg) {}
-
-    setActive(connected) {
-      return this.active = connected;
-    }
+/*
+All of Snowflake's DOM manipulation and inputs.
+*/
 
-    log(msg) {}
+class UI {
 
-  };
+  setStatus(msg) {}
 
-  UI.prototype.active = false;
+  setActive(connected) {
+    return this.active = connected;
+  }
 
-  UI.prototype.enabled = true;
+  log(msg) {}
 
-  return UI;
+};
 
-}).call(this);
+UI.prototype.active = false;
 
-BadgeUI = (function() {
-  class BadgeUI extends UI {
-    constructor() {
-      super();
-      this.$badge = document.getElementById('badge');
-    }
+UI.prototype.enabled = true;
 
-    setActive(connected) {
-      super.setActive(connected);
-      return this.$badge.className = connected ? 'active' : '';
-    }
 
-  };
+class BadgeUI extends UI {
 
-  BadgeUI.prototype.$badge = null;
+  constructor() {
+    super();
+    this.$badge = document.getElementById('badge');
+  }
 
-  return BadgeUI;
+  setActive(connected) {
+    super.setActive(connected);
+    return this.$badge.className = connected ? 'active' : '';
+  }
 
-}).call(this);
+};
 
-DebugUI = (function() {
-  class DebugUI extends UI {
-    constructor() {
-      super();
-      // Setup other DOM handlers if it's debug mode.
-      this.$status = document.getElementById('status');
-      this.$msglog = document.getElementById('msglog');
-      this.$msglog.value = '';
-    }
+BadgeUI.prototype.$badge = null;
 
-    // Status bar
-    setStatus(msg) {
-      var txt;
-      txt = document.createTextNode('Status: ' + msg);
-      while (this.$status.firstChild) {
-        this.$status.removeChild(this.$status.firstChild);
-      }
-      return this.$status.appendChild(txt);
-    }
 
-    setActive(connected) {
-      super.setActive(connected);
-      return this.$msglog.className = connected ? 'active' : '';
-    }
+class DebugUI extends UI {
 
-    log(msg) {
-      // Scroll to latest
-      this.$msglog.value += msg + '\n';
-      return this.$msglog.scrollTop = this.$msglog.scrollHeight;
-    }
+  constructor() {
+    super();
+    // Setup other DOM handlers if it's debug mode.
+    this.$status = document.getElementById('status');
+    this.$msglog = document.getElementById('msglog');
+    this.$msglog.value = '';
+  }
 
-  };
+  // Status bar
+  setStatus(msg) {
+    var txt;
+    txt = document.createTextNode('Status: ' + msg);
+    while (this.$status.firstChild) {
+      this.$status.removeChild(this.$status.firstChild);
+    }
+    return this.$status.appendChild(txt);
+  }
 
-  // DOM elements references.
-  DebugUI.prototype.$msglog = null;
+  setActive(connected) {
+    super.setActive(connected);
+    return this.$msglog.className = connected ? 'active' : '';
+  }
 
-  DebugUI.prototype.$status = null;
+  log(msg) {
+    // Scroll to latest
+    this.$msglog.value += msg + '\n';
+    return this.$msglog.scrollTop = this.$msglog.scrollHeight;
+  }
 
-  return DebugUI;
+};
 
-}).call(this);
+// DOM elements references.
+DebugUI.prototype.$msglog = null;
 
-WebExtUI = (function() {
-  class WebExtUI extends UI {
-    constructor() {
-      super();
-      this.onConnect = this.onConnect.bind(this);
-      this.onMessage = this.onMessage.bind(this);
-      this.onDisconnect = this.onDisconnect.bind(this);
-      this.initStats();
-      chrome.runtime.onConnect.addListener(this.onConnect);
-    }
+DebugUI.prototype.$status = null;
 
-    initStats() {
-      this.stats = [0];
-      return setInterval((() => {
-        this.stats.unshift(0);
-        this.stats.splice(24);
-        return this.postActive();
-      }), 60 * 60 * 1000);
-    }
 
-    initToggle() {
-      var getting;
-      return getting = chrome.storage.local.get("snowflake-enabled", (result) => {
-        if (result['snowflake-enabled'] !== void 0) {
-          this.enabled = result['snowflake-enabled'];
-        } else {
-          log("Toggle state not yet saved");
-        }
-        return this.setEnabled(this.enabled);
-      });
-    }
+class WebExtUI extends UI {
 
-    postActive() {
-      var ref;
-      return (ref = this.port) != null ? ref.postMessage({
-        active: this.active,
-        total: this.stats.reduce((function(t, c) {
-          return t + c;
-        }), 0),
-        enabled: this.enabled
-      }) : void 0;
-    }
+  constructor() {
+    super();
+    this.onConnect = this.onConnect.bind(this);
+    this.onMessage = this.onMessage.bind(this);
+    this.onDisconnect = this.onDisconnect.bind(this);
+    this.initStats();
+    chrome.runtime.onConnect.addListener(this.onConnect);
+  }
 
-    onConnect(port) {
-      boundMethodCheck(this, WebExtUI);
-      this.port = port;
-      port.onDisconnect.addListener(this.onDisconnect);
-      port.onMessage.addListener(this.onMessage);
+  initStats() {
+    this.stats = [0];
+    return setInterval((() => {
+      this.stats.unshift(0);
+      this.stats.splice(24);
       return this.postActive();
-    }
-
-    onMessage(m) {
-      var storing;
-      boundMethodCheck(this, WebExtUI);
-      this.enabled = m.enabled;
-      this.setEnabled(this.enabled);
-      this.postActive();
-      return storing = chrome.storage.local.set({
-        "snowflake-enabled": this.enabled
-      }, function() {
-        return log("Stored toggle state");
-      });
-    }
-
-    onDisconnect(port) {
-      boundMethodCheck(this, WebExtUI);
-      return this.port = null;
-    }
-
-    setActive(connected) {
-      super.setActive(connected);
-      if (connected) {
-        this.stats[0] += 1;
-      }
-      this.postActive();
-      if (this.active) {
-        return chrome.browserAction.setIcon({
-          path: {
-            32: "icons/status-running.png"
-          }
-        });
+    }), 60 * 60 * 1000);
+  }
+
+  initToggle() {
+    var getting;
+    return getting = chrome.storage.local.get("snowflake-enabled", (result) => {
+      if (result['snowflake-enabled'] !== void 0) {
+        this.enabled = result['snowflake-enabled'];
       } else {
-        return chrome.browserAction.setIcon({
-          path: {
-            32: "icons/status-on.png"
-          }
-        });
+        log("Toggle state not yet saved");
       }
-    }
-
-    setEnabled(enabled) {
-      update();
+      return this.setEnabled(this.enabled);
+    });
+  }
+
+  postActive() {
+    var ref;
+    return (ref = this.port) != null ? ref.postMessage({
+      active: this.active,
+      total: this.stats.reduce((function(t, c) {
+        return t + c;
+      }), 0),
+      enabled: this.enabled
+    }) : void 0;
+  }
+
+  onConnect(port) {
+    this.port = port;
+    port.onDisconnect.addListener(this.onDisconnect);
+    port.onMessage.addListener(this.onMessage);
+    return this.postActive();
+  }
+
+  onMessage(m) {
+    var storing;
+    this.enabled = m.enabled;
+    this.setEnabled(this.enabled);
+    this.postActive();
+    return storing = chrome.storage.local.set({
+      "snowflake-enabled": this.enabled
+    }, function() {
+      return log("Stored toggle state");
+    });
+  }
+
+  onDisconnect(port) {
+    return this.port = null;
+  }
+
+  setActive(connected) {
+    super.setActive(connected);
+    if (connected) {
+      this.stats[0] += 1;
+    }
+    this.postActive();
+    if (this.active) {
       return chrome.browserAction.setIcon({
         path: {
-          32: "icons/status-" + (enabled ? "on" : "off") + ".png"
+          32: "icons/status-running.png"
+        }
+      });
+    } else {
+      return chrome.browserAction.setIcon({
+        path: {
+          32: "icons/status-on.png"
         }
       });
     }
+  }
 
-  };
-
-  WebExtUI.prototype.port = null;
+  setEnabled(enabled) {
+    update();
+    return chrome.browserAction.setIcon({
+      path: {
+        32: "icons/status-" + (enabled ? "on" : "off") + ".png"
+      }
+    });
+  }
 
-  WebExtUI.prototype.stats = null;
+};
 
-  return WebExtUI;
+WebExtUI.prototype.port = null;
 
-}).call(this);
+WebExtUI.prototype.stats = null;
diff --git a/proxy/util.js b/proxy/util.js
index 1feeb5d..328fd9b 100644
--- a/proxy/util.js
+++ b/proxy/util.js
@@ -1,53 +1,54 @@
-// Generated by CoffeeScript 2.4.1
 /*
 A Coffeescript WebRTC snowflake proxy
 
 Contains helpers for parsing query strings and other utilities.
 */
-var BucketRateLimit, DummyRateLimit, Params, Parse, Query, Util;
 
-Util = (function() {
-  class Util {
-    static mightBeTBB() {
-      return Util.TBB_UAS.indexOf(window.navigator.userAgent) > -1 && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
-    }
+class Util {
 
-    static genSnowflakeID() {
-      return Math.random().toString(36).substring(2);
-    }
+  static mightBeTBB() {
+    return Util.TBB_UAS.indexOf(window.navigator.userAgent) > -1 && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
+  }
 
-    static snowflakeIsDisabled(cookieName) {
-      var cookies;
-      cookies = Parse.cookie(document.cookie);
-      // Do nothing if snowflake has not been opted in by user.
-      if (cookies[cookieName] !== '1') {
-        log('Not opted-in. Please click the badge to change options.');
-        return true;
-      }
-      // Also do nothing if running in Tor Browser.
-      if (Util.mightBeTBB()) {
-        log('Will not run within Tor Browser.');
-        return true;
-      }
-      return false;
-    }
+  static genSnowflakeID() {
+    return Math.random().toString(36).substring(2);
+  }
 
-    static featureDetect() {
-      return typeof PeerConnection === 'function';
+  static snowflakeIsDisabled(cookieName) {
+    var cookies;
+    cookies = Parse.cookie(document.cookie);
+    // Do nothing if snowflake has not been opted in by user.
+    if (cookies[cookieName] !== '1') {
+      log('Not opted-in. Please click the badge to change options.');
+      return true;
+    }
+    // Also do nothing if running in Tor Browser.
+    if (Util.mightBeTBB()) {
+      log('Will not run within Tor Browser.');
+      return true;
     }
+    return false;
+  }
+
+  static featureDetect() {
+    return typeof PeerConnection === 'function';
+  }
 
-  };
+};
 
-  // It would not be effective for Tor Browser users to run the proxy.
-  // Do we seem to be running in Tor Browser? Check the user-agent string and for
-  // no listing of supported MIME types.
-  Util.TBB_UAS = ['Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0', 'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0', 'Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0', 'Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0'];
+// It would not be effective for Tor Browser users to run the proxy.
+// Do we seem to be running in Tor Browser? Check the user-agent string and for
+// no listing of supported MIME types.
+Util.TBB_UAS = [
+  'Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0',
+  'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0',
+  'Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0',
+  'Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0'
+];
 
-  return Util;
 
-}).call(this);
+class Query {
 
-Query = class Query {
   /*
   Parse a URL query string or application/x-www-form-urlencoded body. The
   return type is an object mapping string keys to string values. By design,
@@ -100,7 +101,9 @@ Query = class Query {
 
 };
 
-Parse = class Parse {
+
+class Parse {
+
   // Parse a cookie data string (usually document.cookie). The return type is an
   // object mapping cookies names to values. Returns null on error.
   // http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-8747038
@@ -202,7 +205,9 @@ Parse = class Parse {
 
 };
 
-Params = class Params {
+
+class Params {
+
   static getBool(query, param, defaultValue) {
     var val;
     val = query[param];
@@ -254,53 +259,52 @@ Params = class Params {
 
 };
 
-BucketRateLimit = (function() {
-  class BucketRateLimit {
-    constructor(capacity, time) {
-      this.capacity = capacity;
-      this.time = time;
-    }
 
-    age() {
-      var delta, now;
-      now = new Date();
-      delta = (now - this.lastUpdate) / 1000.0;
-      this.lastUpdate = now;
-      this.amount -= delta * this.capacity / this.time;
-      if (this.amount < 0.0) {
-        return this.amount = 0.0;
-      }
-    }
+class BucketRateLimit {
 
-    update(n) {
-      this.age();
-      this.amount += n;
-      return this.amount <= this.capacity;
-    }
+  constructor(capacity, time) {
+    this.capacity = capacity;
+    this.time = time;
+  }
 
-    // How many seconds in the future will the limit expire?
-    when() {
-      this.age();
-      return (this.amount - this.capacity) / (this.capacity / this.time);
+  age() {
+    var delta, now;
+    now = new Date();
+    delta = (now - this.lastUpdate) / 1000.0;
+    this.lastUpdate = now;
+    this.amount -= delta * this.capacity / this.time;
+    if (this.amount < 0.0) {
+      return this.amount = 0.0;
     }
+  }
 
-    isLimited() {
-      this.age();
-      return this.amount > this.capacity;
-    }
+  update(n) {
+    this.age();
+    this.amount += n;
+    return this.amount <= this.capacity;
+  }
 
-  };
+  // How many seconds in the future will the limit expire?
+  when() {
+    this.age();
+    return (this.amount - this.capacity) / (this.capacity / this.time);
+  }
 
-  BucketRateLimit.prototype.amount = 0.0;
+  isLimited() {
+    this.age();
+    return this.amount > this.capacity;
+  }
+
+};
 
-  BucketRateLimit.prototype.lastUpdate = new Date();
+BucketRateLimit.prototype.amount = 0.0;
 
-  return BucketRateLimit;
+BucketRateLimit.prototype.lastUpdate = new Date();
 
-}).call(this);
 
 // A rate limiter that never limits.
-DummyRateLimit = class DummyRateLimit {
+class DummyRateLimit {
+
   constructor(capacity, time) {
     this.capacity = capacity;
     this.time = time;
diff --git a/proxy/websocket.js b/proxy/websocket.js
index 9d4ec60..2a124aa 100644
--- a/proxy/websocket.js
+++ b/proxy/websocket.js
@@ -1,70 +1,64 @@
-// Generated by CoffeeScript 2.4.1
 /*
 Only websocket-specific stuff.
 */
-var WS;
 
-WS = (function() {
-  class WS {
-    // Build an escaped URL string from unescaped components. Only scheme and host
-    // are required. See RFC 3986, section 3.
-    static buildUrl(scheme, host, port, path, params) {
-      var parts;
-      parts = [];
-      parts.push(encodeURIComponent(scheme));
-      parts.push('://');
-      // If it contains a colon but no square brackets, treat it as IPv6.
-      if (host.match(/:/) && !host.match(/[[\]]/)) {
-        parts.push('[');
-        parts.push(host);
-        parts.push(']');
-      } else {
-        parts.push(encodeURIComponent(host));
-      }
-      if (void 0 !== port && this.DEFAULT_PORTS[scheme] !== port) {
-        parts.push(':');
-        parts.push(encodeURIComponent(port.toString()));
-      }
-      if (void 0 !== path && '' !== path) {
-        if (!path.match(/^\//)) {
-          path = '/' + path;
-        }
-        path = path.replace(/[^\/]+/, function(m) {
-          return encodeURIComponent(m);
-        });
-        parts.push(path);
-      }
-      if (void 0 !== params) {
-        parts.push('?');
-        parts.push(Query.buildString(params));
+class WS {
+
+  // Build an escaped URL string from unescaped components. Only scheme and host
+  // are required. See RFC 3986, section 3.
+  static buildUrl(scheme, host, port, path, params) {
+    var parts;
+    parts = [];
+    parts.push(encodeURIComponent(scheme));
+    parts.push('://');
+    // If it contains a colon but no square brackets, treat it as IPv6.
+    if (host.match(/:/) && !host.match(/[[\]]/)) {
+      parts.push('[');
+      parts.push(host);
+      parts.push(']');
+    } else {
+      parts.push(encodeURIComponent(host));
+    }
+    if (void 0 !== port && this.DEFAULT_PORTS[scheme] !== port) {
+      parts.push(':');
+      parts.push(encodeURIComponent(port.toString()));
+    }
+    if (void 0 !== path && '' !== path) {
+      if (!path.match(/^\//)) {
+        path = '/' + path;
       }
-      return parts.join('');
+      path = path.replace(/[^\/]+/, function(m) {
+        return encodeURIComponent(m);
+      });
+      parts.push(path);
     }
-
-    static makeWebsocket(addr, params) {
-      var url, ws, wsProtocol;
-      wsProtocol = this.WSS_ENABLED ? 'wss' : 'ws';
-      url = this.buildUrl(wsProtocol, addr.host, addr.port, '/', params);
-      ws = new WebSocket(url);
-      /*
-      'User agents can use this as a hint for how to handle incoming binary data:
-      if the attribute is set to 'blob', it is safe to spool it to disk, and if it
-      is set to 'arraybuffer', it is likely more efficient to keep the data in
-      memory.'
-      */
-      ws.binaryType = 'arraybuffer';
-      return ws;
+    if (void 0 !== params) {
+      parts.push('?');
+      parts.push(Query.buildString(params));
     }
+    return parts.join('');
+  }
 
-  };
-
-  WS.WSS_ENABLED = true;
+  static makeWebsocket(addr, params) {
+    var url, ws, wsProtocol;
+    wsProtocol = this.WSS_ENABLED ? 'wss' : 'ws';
+    url = this.buildUrl(wsProtocol, addr.host, addr.port, '/', params);
+    ws = new WebSocket(url);
+    /*
+    'User agents can use this as a hint for how to handle incoming binary data:
+    if the attribute is set to 'blob', it is safe to spool it to disk, and if it
+    is set to 'arraybuffer', it is likely more efficient to keep the data in
+    memory.'
+    */
+    ws.binaryType = 'arraybuffer';
+    return ws;
+  }
 
-  WS.DEFAULT_PORTS = {
-    http: 80,
-    https: 443
-  };
+};
 
-  return WS;
+WS.WSS_ENABLED = true;
 
-}).call(this);
+WS.DEFAULT_PORTS = {
+  http: 80,
+  https: 443
+};



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits