Lightly massage some of the generated JavaScript

This commit is contained in:
Arlo Breault 2019-07-06 15:20:07 +02:00
parent 31ad9566e6
commit 1867a3f121
19 changed files with 986 additions and 989 deletions

View file

@ -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) {

View file

@ -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 {
// 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;
}
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);
});
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));
}
_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.STATUS = {
OK: 200,
GONE: 410,
GATEWAY_TIMEOUT: 504
};
Broker.STATUS = {
OK: 200,
GONE: 410,
GATEWAY_TIMEOUT: 504
};
Broker.MESSAGE = {
TIMEOUT: 'Timed out waiting for a client offer.',
UNEXPECTED: 'Unexpected status.'
};
Broker.MESSAGE = {
TIMEOUT: 'Timed out waiting for a client offer.',
UNEXPECTED: 'Unexpected status.'
};
Broker.prototype.clients = 0;
return Broker;
}).call(this);
Broker.prototype.clients = 0;

View file

@ -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']
}
]
};

View file

@ -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']) {

View file

@ -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 ==');

View file

@ -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();

View file

@ -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 = [];
}
class ProxyPair {
// 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);
/*
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
}
};
// 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;
]
});
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);
}
try {
err = this.pc.setRemoteDescription(offer);
} catch (error) {
e = error;
log('Invalid SDP message.');
return false;
}
dbg('SDP ' + offer.type + ' successfully received.');
return true;
}
};
// 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;
};
}
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;
receiveWebRTCOffer(offer) {
var e, err;
if ('offer' !== offer.type) {
log('Invalid SDP received -- was not an offer.');
return false;
}
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;
}
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);
try {
err = this.pc.setRemoteDescription(offer);
} catch (error) {
e = error;
log('Invalid SDP message.');
return false;
}
dbg('SDP ' + offer.type + ' successfully received.');
return true;
}
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.');
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();
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;
}
if (this.relayIsReady()) {
this.relay.close();
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;
}
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();
}
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;
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);
}
}
relayIsReady() {
return (null !== this.relay) && (WebSocket.OPEN === this.relay.readyState);
}
webrtcIsReady() {
return null !== this.client && 'open' === this.client.readyState;
}
isClosed(ws) {
return void 0 === ws || WebSocket.CLOSED === ws.readyState;
}
relayIsReady() {
return (null !== this.relay) && (WebSocket.OPEN === this.relay.readyState);
}
};
isClosed(ws) {
return void 0 === ws || WebSocket.CLOSED === ws.readyState;
}
ProxyPair.prototype.MAX_BUFFER = 10 * 1024 * 1024;
};
ProxyPair.prototype.pc = null;
ProxyPair.prototype.MAX_BUFFER = 10 * 1024 * 1024;
ProxyPair.prototype.client = null; // WebRTC Data channel
ProxyPair.prototype.pc = null;
ProxyPair.prototype.relay = null; // websocket
ProxyPair.prototype.client = null; // WebRTC Data channel
ProxyPair.prototype.timer = 0;
ProxyPair.prototype.relay = null; // websocket
ProxyPair.prototype.running = true;
ProxyPair.prototype.timer = 0;
ProxyPair.prototype.active = false; // Whether serving a client.
ProxyPair.prototype.running = true;
ProxyPair.prototype.flush_timeout_id = null;
ProxyPair.prototype.active = false; // Whether serving a client.
ProxyPair.prototype.onCleanup = null;
ProxyPair.prototype.flush_timeout_id = null;
ProxyPair.prototype.id = null;
ProxyPair.prototype.onCleanup = null;
return ProxyPair;
}).call(this);
ProxyPair.prototype.id = null;

View file

@ -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);

View file

@ -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;
// 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;
// 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;
}
// 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);
// Do nothing until a new proxyPair is available.
pair.active = true;
msg = 'Polling for client ... ';
if (this.retries > 0) {
msg += '[retries: ' + this.retries + ']';
}
// 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 {
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++;
}
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);
// 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;
});
}
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);
}
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());
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.relayAddr = null;
Snowflake.prototype.rateLimit = null;
Snowflake.prototype.rateLimit = null;
Snowflake.prototype.pollInterval = null;
Snowflake.prototype.pollInterval = null;
Snowflake.prototype.retries = 0;
Snowflake.prototype.retries = 0;
// Janky state machine
Snowflake.MODE = {
INIT: 0,
WEBRTC_CONNECTING: 1,
WEBRTC_READY: 2
};
// Janky state machine
Snowflake.MODE = {
INIT: 0,
WEBRTC_CONNECTING: 1,
WEBRTC_READY: 2
};
Snowflake.MESSAGE = {
CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
};
return Snowflake;
}).call(this);
Snowflake.MESSAGE = {
CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
};

View file

@ -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();
});
});

View file

@ -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);
});
});

View file

@ -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

View file

@ -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 {};
class SessionDescription {};
SessionDescription.prototype.type = 'offer';
SessionDescription.prototype.type = 'offer';
class WebSocket {
constructor() {
this.bufferedAmount = 0;
}
send(data) {}
};
WebSocket.prototype.OPEN = 1;
WebSocket.prototype.CLOSED = 0;
return SessionDescription;
var log = function() {};
}).call(this);
var config = new Config;
WebSocket = (function() {
class WebSocket {
constructor() {
this.bufferedAmount = 0;
}
var ui = new UI;
send(data) {}
};
WebSocket.prototype.OPEN = 1;
WebSocket.prototype.CLOSED = 0;
return WebSocket;
}).call(this);
log = function() {};
config = new Config;
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);
});
});

View file

@ -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);
});
});

View file

@ -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@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();
});
});
});

View file

@ -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');
});
});

View file

@ -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'); } };
/*
All of Snowflake's DOM manipulation and inputs.
*/
UI = (function() {
class UI {
setStatus(msg) {}
class UI {
setActive(connected) {
return this.active = connected;
setStatus(msg) {}
setActive(connected) {
return this.active = connected;
}
log(msg) {}
};
UI.prototype.active = false;
UI.prototype.enabled = true;
class BadgeUI extends UI {
constructor() {
super();
this.$badge = document.getElementById('badge');
}
setActive(connected) {
super.setActive(connected);
return this.$badge.className = connected ? 'active' : '';
}
};
BadgeUI.prototype.$badge = null;
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 = '';
}
// 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);
}
log(msg) {}
setActive(connected) {
super.setActive(connected);
return this.$msglog.className = connected ? 'active' : '';
}
};
log(msg) {
// Scroll to latest
this.$msglog.value += msg + '\n';
return this.$msglog.scrollTop = this.$msglog.scrollHeight;
}
UI.prototype.active = false;
};
UI.prototype.enabled = true;
// DOM elements references.
DebugUI.prototype.$msglog = null;
return UI;
DebugUI.prototype.$status = null;
}).call(this);
BadgeUI = (function() {
class BadgeUI extends UI {
constructor() {
super();
this.$badge = document.getElementById('badge');
}
class WebExtUI extends UI {
setActive(connected) {
super.setActive(connected);
return this.$badge.className = connected ? 'active' : '';
}
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);
}
};
BadgeUI.prototype.$badge = null;
return BadgeUI;
}).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 = '';
}
// 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' : '';
}
log(msg) {
// Scroll to latest
this.$msglog.value += msg + '\n';
return this.$msglog.scrollTop = this.$msglog.scrollHeight;
}
};
// DOM elements references.
DebugUI.prototype.$msglog = null;
DebugUI.prototype.$status = null;
return DebugUI;
}).call(this);
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);
}
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);
});
}
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) {
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();
}
}), 60 * 60 * 1000);
}
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"
}
});
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");
}
}
return this.setEnabled(this.enabled);
});
}
setEnabled(enabled) {
update();
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"
}
});
}
}
};
setEnabled(enabled) {
update();
return chrome.browserAction.setIcon({
path: {
32: "icons/status-" + (enabled ? "on" : "off") + ".png"
}
});
}
WebExtUI.prototype.port = null;
};
WebExtUI.prototype.stats = null;
WebExtUI.prototype.port = null;
return WebExtUI;
}).call(this);
WebExtUI.prototype.stats = null;

View file

@ -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 mightBeTBB() {
return Util.TBB_UAS.indexOf(window.navigator.userAgent) > -1 && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
}
static genSnowflakeID() {
return Math.random().toString(36).substring(2);
}
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;
}
static genSnowflakeID() {
return Math.random().toString(36).substring(2);
// Also do nothing if running in Tor Browser.
if (Util.mightBeTBB()) {
log('Will not run within Tor Browser.');
return true;
}
return false;
}
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';
}
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;
class Query {
}).call(this);
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;
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;
}
}
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;
}
}
update(n) {
this.age();
this.amount += n;
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);
}
// How many seconds in the future will the limit expire?
when() {
this.age();
return (this.amount - this.capacity) / (this.capacity / this.time);
}
isLimited() {
this.age();
return this.amount > this.capacity;
}
isLimited() {
this.age();
return this.amount > this.capacity;
}
};
};
BucketRateLimit.prototype.amount = 0.0;
BucketRateLimit.prototype.amount = 0.0;
BucketRateLimit.prototype.lastUpdate = new Date();
BucketRateLimit.prototype.lastUpdate = new Date();
return BucketRateLimit;
}).call(this);
// A rate limiter that never limits.
DummyRateLimit = class DummyRateLimit {
class DummyRateLimit {
constructor(capacity, time) {
this.capacity = capacity;
this.time = time;

View file

@ -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));
}
return parts.join('');
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));
}
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 !== 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));
}
return parts.join('');
}
};
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.WSS_ENABLED = true;
};
WS.DEFAULT_PORTS = {
http: 80,
https: 443
};
WS.WSS_ENABLED = true;
return WS;
}).call(this);
WS.DEFAULT_PORTS = {
http: 80,
https: 443
};