commit 1867a3f121b5ea032466675f8ea3d00a134acd9a Author: Arlo Breault arlolra@gmail.com 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/remoteDes... - 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/remoteDes... + 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%5Cne=j.doe@example.com (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://%5B1:2::3:4]'); + expect(WS.buildUrl('http', '1:2::3:4')).toBe('http://%5B1: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@lists.torproject.org