mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
Lightly massage some of the generated JavaScript
This commit is contained in:
parent
31ad9566e6
commit
1867a3f121
19 changed files with 986 additions and 989 deletions
|
@ -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');
|
var fs = require('fs');
|
||||||
|
var { exec, spawn, execSync } = require('child_process');
|
||||||
({exec, spawn, execSync} = require('child_process'));
|
|
||||||
|
|
||||||
// All coffeescript files required.
|
// 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/');
|
return exec('cp ' + STATIC + '/* build/');
|
||||||
};
|
};
|
||||||
|
|
||||||
compileCoffee = function(outDir, init) {
|
var compileCoffee = function(outDir, init) {
|
||||||
var files;
|
var files;
|
||||||
files = FILES.concat('init-' + init + '.coffee');
|
files = FILES.concat('init-' + init + '.coffee');
|
||||||
return exec('cat ' + files.join(' ') + ' | coffee -cs > ' + outDir + '/' + OUTFILE, function(err, stdout, stderr) {
|
return exec('cat ' + files.join(' ') + ' | coffee -cs > ' + outDir + '/' + OUTFILE, function(err, stdout, stderr) {
|
||||||
|
|
188
proxy/broker.js
188
proxy/broker.js
|
@ -1,73 +1,44 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Communication with the snowflake broker.
|
Communication with the snowflake broker.
|
||||||
|
|
||||||
Browser snowflakes must register with the broker in order
|
Browser snowflakes must register with the broker in order
|
||||||
to get assigned to clients.
|
to get assigned to clients.
|
||||||
*/
|
*/
|
||||||
var Broker;
|
|
||||||
|
|
||||||
Broker = (function() {
|
// Represents a broker running remotely.
|
||||||
// Represents a broker running remotely.
|
class Broker {
|
||||||
class Broker {
|
|
||||||
// When interacting with the Broker, snowflake must generate a unique session
|
// When interacting with the Broker, snowflake must generate a unique session
|
||||||
// ID so the Broker can keep track of each proxy's signalling channels.
|
// ID so the Broker can keep track of each proxy's signalling channels.
|
||||||
// On construction, this Broker object does not do anything until
|
// On construction, this Broker object does not do anything until
|
||||||
// |getClientOffer| is called.
|
// |getClientOffer| is called.
|
||||||
constructor(url) {
|
constructor(url) {
|
||||||
// Promises some client SDP Offer.
|
// Promises some client SDP Offer.
|
||||||
// Registers this Snowflake with the broker using an HTTP POST request, and
|
// Registers this Snowflake with the broker using an HTTP POST request, and
|
||||||
// waits for a response containing some client offer that the Broker chooses
|
// waits for a response containing some client offer that the Broker chooses
|
||||||
// for this proxy..
|
// for this proxy..
|
||||||
// TODO: Actually support multiple clients.
|
// TODO: Actually support multiple clients.
|
||||||
this.getClientOffer = this.getClientOffer.bind(this);
|
this.getClientOffer = this.getClientOffer.bind(this);
|
||||||
// urlSuffix for the broker is different depending on what action
|
// urlSuffix for the broker is different depending on what action
|
||||||
// is desired.
|
// is desired.
|
||||||
this._postRequest = this._postRequest.bind(this);
|
this._postRequest = this._postRequest.bind(this);
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.clients = 0;
|
this.clients = 0;
|
||||||
if (0 === this.url.indexOf('localhost', 0)) {
|
if (0 === this.url.indexOf('localhost', 0)) {
|
||||||
// Ensure url has the right protocol + trailing slash.
|
// Ensure url has the right protocol + trailing slash.
|
||||||
this.url = 'http://' + this.url;
|
this.url = 'http://' + this.url;
|
||||||
}
|
|
||||||
if (0 !== this.url.indexOf('http', 0)) {
|
|
||||||
this.url = 'https://' + this.url;
|
|
||||||
}
|
|
||||||
if ('/' !== this.url.substr(-1)) {
|
|
||||||
this.url += '/';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (0 !== this.url.indexOf('http', 0)) {
|
||||||
getClientOffer(id) {
|
this.url = 'https://' + this.url;
|
||||||
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 ('/' !== this.url.substr(-1)) {
|
||||||
|
this.url += '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
|
getClientOffer(id) {
|
||||||
// Sends it back to the broker, which passes it to back to the original client.
|
return new Promise((fulfill, reject) => {
|
||||||
sendAnswer(id, answer) {
|
|
||||||
var xhr;
|
var xhr;
|
||||||
dbg(id + ' - Sending answer back to broker...\n');
|
|
||||||
dbg(answer.sdp);
|
|
||||||
xhr = new XMLHttpRequest();
|
xhr = new XMLHttpRequest();
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.DONE !== xhr.readyState) {
|
if (xhr.DONE !== xhr.readyState) {
|
||||||
|
@ -75,52 +46,75 @@ Broker = (function() {
|
||||||
}
|
}
|
||||||
switch (xhr.status) {
|
switch (xhr.status) {
|
||||||
case Broker.STATUS.OK:
|
case Broker.STATUS.OK:
|
||||||
dbg('Broker: Successfully replied with answer.');
|
return fulfill(xhr.responseText); // Should contain offer.
|
||||||
return dbg(xhr.responseText);
|
case Broker.STATUS.GATEWAY_TIMEOUT:
|
||||||
case Broker.STATUS.GONE:
|
return reject(Broker.MESSAGE.TIMEOUT);
|
||||||
return dbg('Broker: No longer valid to reply with answer.');
|
|
||||||
default:
|
default:
|
||||||
dbg('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
|
log('Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText);
|
||||||
return snowflake.ui.setStatus(' failure. Please refresh.');
|
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) {
|
// Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
|
||||||
var err;
|
// Sends it back to the broker, which passes it to back to the original client.
|
||||||
try {
|
sendAnswer(id, answer) {
|
||||||
xhr.open('POST', this.url + urlSuffix);
|
var xhr;
|
||||||
xhr.setRequestHeader('X-Session-ID', id);
|
dbg(id + ' - Sending answer back to broker...\n');
|
||||||
} catch (error) {
|
dbg(answer.sdp);
|
||||||
err = error;
|
xhr = new XMLHttpRequest();
|
||||||
/*
|
xhr.onreadystatechange = function() {
|
||||||
An exception happens here when, for example, NoScript allows the domain
|
if (xhr.DONE !== xhr.readyState) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
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 = {
|
Broker.STATUS = {
|
||||||
OK: 200,
|
OK: 200,
|
||||||
GONE: 410,
|
GONE: 410,
|
||||||
GATEWAY_TIMEOUT: 504
|
GATEWAY_TIMEOUT: 504
|
||||||
};
|
};
|
||||||
|
|
||||||
Broker.MESSAGE = {
|
Broker.MESSAGE = {
|
||||||
TIMEOUT: 'Timed out waiting for a client offer.',
|
TIMEOUT: 'Timed out waiting for a client offer.',
|
||||||
UNEXPECTED: 'Unexpected status.'
|
UNEXPECTED: 'Unexpected status.'
|
||||||
};
|
};
|
||||||
|
|
||||||
Broker.prototype.clients = 0;
|
Broker.prototype.clients = 0;
|
||||||
|
|
||||||
return Broker;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
|
@ -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 = {
|
Config.prototype.relayAddr = {
|
||||||
host: 'snowflake.bamsoftware.com',
|
host: 'snowflake.bamsoftware.com',
|
||||||
port: '443'
|
port: '443'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Original non-wss relay:
|
// Original non-wss relay:
|
||||||
// host: '192.81.135.242'
|
// host: '192.81.135.242'
|
||||||
// port: 9902
|
// port: 9902
|
||||||
Config.prototype.cookieName = "snowflake-allow";
|
Config.prototype.cookieName = "snowflake-allow";
|
||||||
|
|
||||||
// Bytes per second. Set to undefined to disable limit.
|
// Bytes per second. Set to undefined to disable limit.
|
||||||
Config.prototype.rateLimitBytes = void 0;
|
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.
|
// TODO: Different ICE servers.
|
||||||
Config.prototype.pcConfig = {
|
Config.prototype.pcConfig = {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: ['stun:stun.l.google.com:19302']
|
urls: ['stun:stun.l.google.com:19302']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
return Config;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
|
@ -1,37 +1,35 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Entry point.
|
Entry point.
|
||||||
*/
|
*/
|
||||||
var dbg, debug, init, log, query, silenceNotifications, snowflake;
|
|
||||||
|
|
||||||
if (((typeof TESTING === "undefined" || TESTING === null) || !TESTING) && !Util.featureDetect()) {
|
if (((typeof TESTING === "undefined" || TESTING === null) || !TESTING) && !Util.featureDetect()) {
|
||||||
console.log('webrtc feature not detected. shutting down');
|
console.log('webrtc feature not detected. shutting down');
|
||||||
return;
|
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.
|
// Log to both console and UI if applicable.
|
||||||
// Requires that the snowflake and UI objects are hooked up in order to
|
// Requires that the snowflake and UI objects are hooked up in order to
|
||||||
// log to console.
|
// log to console.
|
||||||
log = function(msg) {
|
var log = function(msg) {
|
||||||
console.log('Snowflake: ' + msg);
|
console.log('Snowflake: ' + msg);
|
||||||
return snowflake != null ? snowflake.ui.log(msg) : void 0;
|
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)) {
|
if (debug || ((snowflake != null ? snowflake.ui : void 0) instanceof DebugUI)) {
|
||||||
return log(msg);
|
return log(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
init = function() {
|
var init = function() {
|
||||||
var broker, config, ui;
|
var broker, config, ui;
|
||||||
config = new Config;
|
config = new Config;
|
||||||
if ('off' !== query['ratelimit']) {
|
if ('off' !== query['ratelimit']) {
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Entry point.
|
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);
|
return console.log('Snowflake: ' + msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg = log;
|
var dbg = log;
|
||||||
|
|
||||||
log('== snowflake proxy ==');
|
log('== snowflake proxy ==');
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Entry point.
|
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.
|
// Log to both console and UI if applicable.
|
||||||
// Requires that the snowflake and UI objects are hooked up in order to
|
// Requires that the snowflake and UI objects are hooked up in order to
|
||||||
// log to console.
|
// log to console.
|
||||||
log = function(msg) {
|
var log = function(msg) {
|
||||||
console.log('Snowflake: ' + msg);
|
console.log('Snowflake: ' + msg);
|
||||||
return snowflake != null ? snowflake.ui.log(msg) : void 0;
|
return snowflake != null ? snowflake.ui.log(msg) : void 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg = function(msg) {
|
var dbg = function(msg) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
return log(msg);
|
return log(msg);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +35,7 @@ if (!Util.featureDetect()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
init = function() {
|
var init = function() {
|
||||||
config = new Config;
|
config = new Config;
|
||||||
ui = new WebExtUI();
|
ui = new WebExtUI();
|
||||||
broker = new Broker(config.brokerUrl);
|
broker = new Broker(config.brokerUrl);
|
||||||
|
@ -46,7 +44,7 @@ init = function() {
|
||||||
return ui.initToggle();
|
return ui.initToggle();
|
||||||
};
|
};
|
||||||
|
|
||||||
update = function() {
|
var update = function() {
|
||||||
if (!ui.enabled) {
|
if (!ui.enabled) {
|
||||||
// Do not activate the proxy if any number of conditions are true.
|
// Do not activate the proxy if any number of conditions are true.
|
||||||
snowflake.disable();
|
snowflake.disable();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Represents a single:
|
Represents a single:
|
||||||
|
|
||||||
|
@ -7,256 +6,251 @@ Represents a single:
|
||||||
Every ProxyPair has a Snowflake ID, which is necessary when responding to the
|
Every ProxyPair has a Snowflake ID, which is necessary when responding to the
|
||||||
Broker with an WebRTC answer.
|
Broker with an WebRTC answer.
|
||||||
*/
|
*/
|
||||||
var ProxyPair;
|
|
||||||
|
|
||||||
ProxyPair = (function() {
|
class ProxyPair {
|
||||||
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() {
|
Constructs a ProxyPair where:
|
||||||
this.pc = new PeerConnection(this.pcConfig, {
|
- @relayAddr is the destination relay
|
||||||
optional: [
|
- @rateLimit specifies a rate limit on traffic
|
||||||
{
|
*/
|
||||||
DtlsSrtpKeyAgreement: true
|
constructor(relayAddr, rateLimit, pcConfig) {
|
||||||
},
|
// Given a WebRTC DataChannel, prepare callbacks.
|
||||||
{
|
this.prepareDataChannel = this.prepareDataChannel.bind(this);
|
||||||
RtpDataChannels: false
|
// Assumes WebRTC datachannel is connected.
|
||||||
}
|
this.connectRelay = this.connectRelay.bind(this);
|
||||||
]
|
// WebRTC --> websocket
|
||||||
});
|
this.onClientToRelayMessage = this.onClientToRelayMessage.bind(this);
|
||||||
this.pc.onicecandidate = (evt) => {
|
// websocket --> WebRTC
|
||||||
// Browser sends a null candidate once the ICE gathering completes.
|
this.onRelayToClientMessage = this.onRelayToClientMessage.bind(this);
|
||||||
if (null === evt.candidate) {
|
this.onError = this.onError.bind(this);
|
||||||
// TODO: Use a promise.all to tell Snowflake about all offers at once,
|
// Send as much data in both directions as the rate limit currently allows.
|
||||||
// once multiple proxypairs are supported.
|
this.flush = this.flush.bind(this);
|
||||||
dbg('Finished gathering ICE candidates.');
|
this.relayAddr = relayAddr;
|
||||||
return snowflake.broker.sendAnswer(this.id, this.pc.localDescription);
|
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) => {
|
this.pc.onicecandidate = (evt) => {
|
||||||
var channel;
|
// Browser sends a null candidate once the ICE gathering completes.
|
||||||
channel = dc.channel;
|
if (null === evt.candidate) {
|
||||||
dbg('Data Channel established...');
|
// TODO: Use a promise.all to tell Snowflake about all offers at once,
|
||||||
this.prepareDataChannel(channel);
|
// once multiple proxypairs are supported.
|
||||||
return this.client = channel;
|
dbg('Finished gathering ICE candidates.');
|
||||||
};
|
return snowflake.broker.sendAnswer(this.id, this.pc.localDescription);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
// OnDataChannel triggered remotely from the client when connection succeeds.
|
||||||
} catch (error) {
|
return this.pc.ondatachannel = (dc) => {
|
||||||
e = error;
|
var channel;
|
||||||
log('Invalid SDP message.');
|
channel = dc.channel;
|
||||||
return false;
|
dbg('Data Channel established...');
|
||||||
}
|
this.prepareDataChannel(channel);
|
||||||
dbg('SDP ' + offer.type + ' successfully received.');
|
return this.client = channel;
|
||||||
return true;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDataChannel(channel) {
|
receiveWebRTCOffer(offer) {
|
||||||
channel.onopen = () => {
|
var e, err;
|
||||||
log('WebRTC DataChannel opened!');
|
if ('offer' !== offer.type) {
|
||||||
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
log('Invalid SDP received -- was not an offer.');
|
||||||
snowflake.ui.setActive(true);
|
return false;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
connectRelay() {
|
err = this.pc.setRemoteDescription(offer);
|
||||||
var params, peer_ip, ref;
|
} catch (error) {
|
||||||
dbg('Connecting to relay...');
|
e = error;
|
||||||
// Get a remote IP address from the PeerConnection, if possible. Add it to
|
log('Invalid SDP message.');
|
||||||
// the WebSocket URL's query string if available.
|
return false;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
dbg('SDP ' + offer.type + ' successfully received.');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
onClientToRelayMessage(msg) {
|
prepareDataChannel(channel) {
|
||||||
dbg('WebRTC --> websocket data: ' + msg.data.byteLength + ' bytes');
|
channel.onopen = () => {
|
||||||
this.c2rSchedule.push(msg.data);
|
log('WebRTC DataChannel opened!');
|
||||||
return this.flush();
|
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
||||||
}
|
snowflake.ui.setActive(true);
|
||||||
|
// This is the point when the WebRTC datachannel is done, so the next step
|
||||||
onRelayToClientMessage(event) {
|
// is to establish websocket to the server.
|
||||||
dbg('websocket --> WebRTC data: ' + event.data.byteLength + ' bytes');
|
return this.connectRelay();
|
||||||
this.r2cSchedule.push(event.data);
|
};
|
||||||
return this.flush();
|
channel.onclose = () => {
|
||||||
}
|
log('WebRTC DataChannel closed.');
|
||||||
|
snowflake.ui.setStatus('disconnected by webrtc.');
|
||||||
onError(event) {
|
snowflake.ui.setActive(false);
|
||||||
var ws;
|
snowflake.state = Snowflake.MODE.INIT;
|
||||||
ws = event.target;
|
this.flush();
|
||||||
log(ws.label + ' error.');
|
|
||||||
return this.close();
|
return this.close();
|
||||||
}
|
};
|
||||||
|
channel.onerror = function() {
|
||||||
|
return log('Data channel error!');
|
||||||
|
};
|
||||||
|
channel.binaryType = "arraybuffer";
|
||||||
|
return channel.onmessage = this.onClientToRelayMessage;
|
||||||
|
}
|
||||||
|
|
||||||
// Close both WebRTC and websocket.
|
connectRelay() {
|
||||||
close() {
|
var params, peer_ip, ref;
|
||||||
var relay;
|
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) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
this.timer = 0;
|
this.timer = 0;
|
||||||
}
|
}
|
||||||
this.running = false;
|
log(this.relay.label + ' connected!');
|
||||||
if (this.webrtcIsReady()) {
|
return snowflake.ui.setStatus('connected');
|
||||||
this.client.close();
|
};
|
||||||
|
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()) {
|
log(this.relay.label + ' timed out connecting.');
|
||||||
this.relay.close();
|
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;
|
// websocket --> WebRTC
|
||||||
return this.onCleanup();
|
if (this.webrtcIsReady() && this.client.bufferedAmount < this.MAX_BUFFER && this.r2cSchedule.length > 0) {
|
||||||
}
|
chunk = this.r2cSchedule.shift();
|
||||||
|
this.rateLimit.update(chunk.byteLength);
|
||||||
flush() {
|
this.client.send(chunk);
|
||||||
var busy, checkChunks;
|
return busy = true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
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)) {
|
||||||
webrtcIsReady() {
|
return this.flush_timeout_id = setTimeout(this.flush, this.rateLimit.when() * 1000);
|
||||||
return null !== this.client && 'open' === this.client.readyState;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
relayIsReady() {
|
webrtcIsReady() {
|
||||||
return (null !== this.relay) && (WebSocket.OPEN === this.relay.readyState);
|
return null !== this.client && 'open' === this.client.readyState;
|
||||||
}
|
}
|
||||||
|
|
||||||
isClosed(ws) {
|
relayIsReady() {
|
||||||
return void 0 === ws || WebSocket.CLOSED === ws.readyState;
|
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;
|
ProxyPair.prototype.id = null;
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
WebRTC shims for multiple browsers.
|
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) {
|
if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
|
||||||
window = {};
|
window = {};
|
||||||
|
@ -19,10 +17,9 @@ if (typeof module !== "undefined" && module !== null ? module.exports : void 0)
|
||||||
IceCandidate = webrtc.RTCIceCandidate;
|
IceCandidate = webrtc.RTCIceCandidate;
|
||||||
SessionDescription = webrtc.RTCSessionDescription;
|
SessionDescription = webrtc.RTCSessionDescription;
|
||||||
WebSocket = require('ws');
|
WebSocket = require('ws');
|
||||||
({XMLHttpRequest} = require('xmlhttprequest'));
|
({ XMLHttpRequest } = require('xmlhttprequest'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window = this;
|
|
||||||
document = window.document;
|
document = window.document;
|
||||||
chrome = window.chrome;
|
chrome = window.chrome;
|
||||||
location = window.location.search.substr(1);
|
location = window.location.search.substr(1);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
A Coffeescript WebRTC snowflake proxy
|
A Coffeescript WebRTC snowflake proxy
|
||||||
|
|
||||||
|
@ -9,174 +8,169 @@ this proxy must always act as the answerer.
|
||||||
|
|
||||||
TODO: More documentation
|
TODO: More documentation
|
||||||
*/
|
*/
|
||||||
var Snowflake;
|
|
||||||
|
|
||||||
Snowflake = (function() {
|
// Minimum viable snowflake for now - just 1 client.
|
||||||
// Minimum viable snowflake for now - just 1 client.
|
class Snowflake {
|
||||||
class Snowflake {
|
|
||||||
// Prepare the Snowflake with a Broker (to find clients) and optional UI.
|
// Prepare the Snowflake with a Broker (to find clients) and optional UI.
|
||||||
constructor(config, ui, broker) {
|
constructor(config, ui, broker) {
|
||||||
// Receive an SDP offer from some client assigned by the Broker,
|
// Receive an SDP offer from some client assigned by the Broker,
|
||||||
// |pair| - an available ProxyPair.
|
// |pair| - an available ProxyPair.
|
||||||
this.receiveOffer = this.receiveOffer.bind(this);
|
this.receiveOffer = this.receiveOffer.bind(this);
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
this.broker = broker;
|
this.broker = broker;
|
||||||
this.state = Snowflake.MODE.INIT;
|
this.state = Snowflake.MODE.INIT;
|
||||||
this.proxyPairs = [];
|
this.proxyPairs = [];
|
||||||
if (void 0 === this.config.rateLimitBytes) {
|
if (void 0 === this.config.rateLimitBytes) {
|
||||||
this.rateLimit = new DummyRateLimit();
|
this.rateLimit = new DummyRateLimit();
|
||||||
} else {
|
} else {
|
||||||
this.rateLimit = new BucketRateLimit(this.config.rateLimitBytes * this.config.rateLimitHistory, this.config.rateLimitHistory);
|
this.rateLimit = new BucketRateLimit(this.config.rateLimitBytes * this.config.rateLimitHistory, this.config.rateLimitHistory);
|
||||||
}
|
|
||||||
this.retries = 0;
|
|
||||||
}
|
}
|
||||||
|
this.retries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the target relay address spec, which is expected to be websocket.
|
// Set the target relay address spec, which is expected to be websocket.
|
||||||
// TODO: Should potentially fetch the target from broker later, or modify
|
// TODO: Should potentially fetch the target from broker later, or modify
|
||||||
// entirely for the Tor-independent version.
|
// entirely for the Tor-independent version.
|
||||||
setRelayAddr(relayAddr) {
|
setRelayAddr(relayAddr) {
|
||||||
this.relayAddr = relayAddr;
|
this.relayAddr = relayAddr;
|
||||||
log('Using ' + relayAddr.host + ':' + relayAddr.port + ' as Relay.');
|
log('Using ' + relayAddr.host + ':' + relayAddr.port + ' as Relay.');
|
||||||
return true;
|
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;
|
||||||
}
|
}
|
||||||
|
// Do nothing until a new proxyPair is available.
|
||||||
// Initialize WebRTC PeerConnection, which requires beginning the signalling
|
pair.active = true;
|
||||||
// process. |pollBroker| automatically arranges signalling.
|
msg = 'Polling for client ... ';
|
||||||
beginWebRTC() {
|
if (this.retries > 0) {
|
||||||
this.state = Snowflake.MODE.WEBRTC_CONNECTING;
|
msg += '[retries: ' + this.retries + ']';
|
||||||
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.ui.setStatus(msg);
|
||||||
// Regularly poll Broker for clients to serve until this snowflake is
|
recv = this.broker.getClientOffer(pair.id);
|
||||||
// serving at capacity, at which point stop polling.
|
recv.then((desc) => {
|
||||||
pollBroker() {
|
if (pair.running) {
|
||||||
var msg, pair, recv;
|
if (!this.receiveOffer(pair, desc)) {
|
||||||
// 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 {
|
|
||||||
return pair.active = false;
|
return pair.active = false;
|
||||||
}
|
}
|
||||||
}, function(err) {
|
} else {
|
||||||
return pair.active = false;
|
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) {
|
}, function(err) {
|
||||||
return !pp.active;
|
return pair.active = false;
|
||||||
});
|
});
|
||||||
}
|
return this.retries++;
|
||||||
|
}
|
||||||
|
|
||||||
receiveOffer(pair, desc) {
|
// Returns the first ProxyPair that's available to connect.
|
||||||
var e, offer, sdp;
|
nextAvailableProxyPair() {
|
||||||
try {
|
if (this.proxyPairs.length < this.config.connectionsPerClient) {
|
||||||
offer = JSON.parse(desc);
|
return this.makeProxyPair(this.relayAddr);
|
||||||
dbg('Received:\n\n' + offer.sdp + '\n');
|
}
|
||||||
sdp = new SessionDescription(offer);
|
return this.proxyPairs.find(function(pp, i, arr) {
|
||||||
if (pair.receiveWebRTCOffer(sdp)) {
|
return !pp.active;
|
||||||
this.sendAnswer(pair);
|
});
|
||||||
return true;
|
}
|
||||||
} else {
|
|
||||||
return false;
|
receiveOffer(pair, desc) {
|
||||||
}
|
var e, offer, sdp;
|
||||||
} catch (error) {
|
try {
|
||||||
e = error;
|
offer = JSON.parse(desc);
|
||||||
log('ERROR: Unable to receive Offer: ' + e);
|
dbg('Received:\n\n' + offer.sdp + '\n');
|
||||||
|
sdp = new SessionDescription(offer);
|
||||||
|
if (pair.receiveWebRTCOffer(sdp)) {
|
||||||
|
this.sendAnswer(pair);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
e = error;
|
||||||
|
log('ERROR: Unable to receive Offer: ' + e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendAnswer(pair) {
|
sendAnswer(pair) {
|
||||||
var fail, next;
|
var fail, next;
|
||||||
next = function(sdp) {
|
next = function(sdp) {
|
||||||
dbg('webrtc: Answer ready.');
|
dbg('webrtc: Answer ready.');
|
||||||
return pair.pc.setLocalDescription(sdp);
|
return pair.pc.setLocalDescription(sdp);
|
||||||
};
|
};
|
||||||
fail = function() {
|
fail = function() {
|
||||||
return dbg('webrtc: Failed to create Answer');
|
return dbg('webrtc: Failed to create Answer');
|
||||||
};
|
};
|
||||||
return pair.pc.createAnswer().then(next).catch(fail);
|
return pair.pc.createAnswer().then(next).catch(fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeProxyPair(relay) {
|
makeProxyPair(relay) {
|
||||||
var pair;
|
var pair;
|
||||||
pair = new ProxyPair(relay, this.rateLimit, this.config.pcConfig);
|
pair = new ProxyPair(relay, this.rateLimit, this.config.pcConfig);
|
||||||
this.proxyPairs.push(pair);
|
this.proxyPairs.push(pair);
|
||||||
pair.onCleanup = (event) => {
|
pair.onCleanup = (event) => {
|
||||||
var ind;
|
var ind;
|
||||||
// Delete from the list of active proxy pairs.
|
// Delete from the list of active proxy pairs.
|
||||||
ind = this.proxyPairs.indexOf(pair);
|
ind = this.proxyPairs.indexOf(pair);
|
||||||
if (ind > -1) {
|
if (ind > -1) {
|
||||||
return this.proxyPairs.splice(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());
|
|
||||||
}
|
}
|
||||||
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
|
// Janky state machine
|
||||||
Snowflake.MODE = {
|
Snowflake.MODE = {
|
||||||
INIT: 0,
|
INIT: 0,
|
||||||
WEBRTC_CONNECTING: 1,
|
WEBRTC_CONNECTING: 1,
|
||||||
WEBRTC_READY: 2
|
WEBRTC_READY: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
Snowflake.MESSAGE = {
|
Snowflake.MESSAGE = {
|
||||||
CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
|
CONFIRMATION: 'You\'re currently serving a Tor user via Snowflake.'
|
||||||
};
|
};
|
||||||
|
|
||||||
return Snowflake;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
|
@ -1,39 +1,32 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake broker
|
jasmine tests for Snowflake broker
|
||||||
*/
|
*/
|
||||||
var XMLHttpRequest;
|
|
||||||
|
|
||||||
XMLHttpRequest = (function() {
|
// fake xhr
|
||||||
// fake xhr
|
// class XMLHttpRequest
|
||||||
// class XMLHttpRequest
|
class XMLHttpRequest {
|
||||||
class XMLHttpRequest {
|
constructor() {
|
||||||
constructor() {
|
this.onreadystatechange = null;
|
||||||
this.onreadystatechange = null;
|
}
|
||||||
}
|
open() {}
|
||||||
|
setRequestHeader() {}
|
||||||
|
send() {}
|
||||||
|
};
|
||||||
|
|
||||||
open() {}
|
XMLHttpRequest.prototype.DONE = 1;
|
||||||
|
|
||||||
setRequestHeader() {}
|
|
||||||
|
|
||||||
send() {}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
XMLHttpRequest.prototype.DONE = 1;
|
|
||||||
|
|
||||||
return XMLHttpRequest;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
describe('Broker', function() {
|
describe('Broker', function() {
|
||||||
|
|
||||||
it('can be created', function() {
|
it('can be created', function() {
|
||||||
var b;
|
var b;
|
||||||
b = new Broker('fake');
|
b = new Broker('fake');
|
||||||
expect(b.url).toEqual('https://fake/');
|
expect(b.url).toEqual('https://fake/');
|
||||||
return expect(b.id).not.toBeNull();
|
expect(b.id).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getClientOffer', function() {
|
describe('getClientOffer', function() {
|
||||||
|
|
||||||
it('polls and promises a client offer', function(done) {
|
it('polls and promises a client offer', function(done) {
|
||||||
var b, poll;
|
var b, poll;
|
||||||
b = new Broker('fake');
|
b = new Broker('fake');
|
||||||
|
@ -55,6 +48,7 @@ describe('Broker', function() {
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects if the broker timed-out', function(done) {
|
it('rejects if the broker timed-out', function(done) {
|
||||||
var b, poll;
|
var b, poll;
|
||||||
b = new Broker('fake');
|
b = new Broker('fake');
|
||||||
|
@ -75,7 +69,8 @@ describe('Broker', function() {
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return it('rejects on any other status', function(done) {
|
|
||||||
|
it('rejects on any other status', function(done) {
|
||||||
var b, poll;
|
var b, poll;
|
||||||
b = new Broker('fake');
|
b = new Broker('fake');
|
||||||
// fake timed-out request from broker
|
// fake timed-out request from broker
|
||||||
|
@ -95,18 +90,20 @@ describe('Broker', function() {
|
||||||
expect(b._xhr.status).toBe(1337);
|
expect(b._xhr.status).toBe(1337);
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('responds to the broker with answer', function() {
|
it('responds to the broker with answer', function() {
|
||||||
var b;
|
var b = new Broker('fake');
|
||||||
b = new Broker('fake');
|
|
||||||
spyOn(b, '_postRequest');
|
spyOn(b, '_postRequest');
|
||||||
b.sendAnswer('fake id', 123);
|
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;
|
it('POST XMLHttpRequests to the broker', function() {
|
||||||
b = new Broker('fake');
|
var b = new Broker('fake');
|
||||||
b._xhr = new XMLHttpRequest();
|
b._xhr = new XMLHttpRequest();
|
||||||
spyOn(b._xhr, 'open');
|
spyOn(b._xhr, 'open');
|
||||||
spyOn(b._xhr, 'setRequestHeader');
|
spyOn(b._xhr, 'setRequestHeader');
|
||||||
|
@ -114,6 +111,7 @@ describe('Broker', function() {
|
||||||
b._postRequest(0, b._xhr, 'test', 'data');
|
b._postRequest(0, b._xhr, 'test', 'data');
|
||||||
expect(b._xhr.open).toHaveBeenCalled();
|
expect(b._xhr.open).toHaveBeenCalled();
|
||||||
expect(b._xhr.setRequestHeader).toHaveBeenCalled();
|
expect(b._xhr.setRequestHeader).toHaveBeenCalled();
|
||||||
return expect(b._xhr.send).toHaveBeenCalled();
|
expect(b._xhr.send).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
// Fake snowflake to interact with
|
// Fake snowflake to interact with
|
||||||
var snowflake;
|
|
||||||
|
|
||||||
snowflake = {
|
var snowflake = {
|
||||||
ui: new UI,
|
ui: new UI,
|
||||||
broker: {
|
broker: {
|
||||||
sendAnswer: function() {}
|
sendAnswer: function() {}
|
||||||
|
@ -11,24 +9,25 @@ snowflake = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Init', function() {
|
describe('Init', function() {
|
||||||
|
|
||||||
it('gives a dialog when closing, only while active', function() {
|
it('gives a dialog when closing, only while active', function() {
|
||||||
var msg, silenceNotifications;
|
|
||||||
silenceNotifications = false;
|
silenceNotifications = false;
|
||||||
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
||||||
msg = window.onbeforeunload();
|
var msg = window.onbeforeunload();
|
||||||
expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
|
expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
|
||||||
expect(msg).toBe(Snowflake.MESSAGE.CONFIRMATION);
|
expect(msg).toBe(Snowflake.MESSAGE.CONFIRMATION);
|
||||||
snowflake.state = Snowflake.MODE.INIT;
|
snowflake.state = Snowflake.MODE.INIT;
|
||||||
msg = window.onbeforeunload();
|
msg = window.onbeforeunload();
|
||||||
expect(snowflake.state).toBe(Snowflake.MODE.INIT);
|
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;
|
silenceNotifications = true;
|
||||||
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
snowflake.state = Snowflake.MODE.WEBRTC_READY;
|
||||||
msg = window.onbeforeunload();
|
var msg = window.onbeforeunload();
|
||||||
expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
|
expect(snowflake.state).toBe(Snowflake.MODE.WEBRTC_READY);
|
||||||
return expect(msg).toBe(null);
|
expect(msg).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake proxypair
|
jasmine tests for Snowflake proxypair
|
||||||
*/
|
*/
|
||||||
var MessageEvent, arrayMatching;
|
|
||||||
|
|
||||||
// Replacement for MessageEvent constructor.
|
// Replacement for MessageEvent constructor.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/MessageEvent
|
// https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/MessageEvent
|
||||||
MessageEvent = function(type, init) {
|
var MessageEvent = function(type, init) {
|
||||||
return init;
|
return init;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Asymmetic matcher that checks that two arrays have the same contents.
|
// Asymmetic matcher that checks that two arrays have the same contents.
|
||||||
arrayMatching = function(sample) {
|
var arrayMatching = function(sample) {
|
||||||
return {
|
return {
|
||||||
asymmetricMatch: function(other) {
|
asymmetricMatch: function(other) {
|
||||||
var _, a, b, i, j, len;
|
var _, a, b, i, j, len;
|
||||||
|
@ -35,46 +33,57 @@ arrayMatching = function(sample) {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('ProxyPair', function() {
|
describe('ProxyPair', function() {
|
||||||
|
|
||||||
var config, destination, fakeRelay, pp, rateLimit;
|
var config, destination, fakeRelay, pp, rateLimit;
|
||||||
fakeRelay = Parse.address('0.0.0.0:12345');
|
fakeRelay = Parse.address('0.0.0.0:12345');
|
||||||
rateLimit = new DummyRateLimit;
|
rateLimit = new DummyRateLimit;
|
||||||
config = new Config;
|
config = new Config;
|
||||||
destination = [];
|
destination = [];
|
||||||
|
|
||||||
// Using the mock PeerConnection definition from spec/snowflake.spec.coffee.
|
// 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() {
|
beforeEach(function() {
|
||||||
return pp.begin();
|
return pp.begin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('begins webrtc connection', function() {
|
it('begins webrtc connection', function() {
|
||||||
return expect(pp.pc).not.toBeNull();
|
return expect(pp.pc).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('accepts WebRTC offer from some client', function() {
|
describe('accepts WebRTC offer from some client', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
return pp.begin();
|
return pp.begin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects invalid offers', function() {
|
it('rejects invalid offers', function() {
|
||||||
expect(typeof pp.pc.setRemoteDescription).toBe("function");
|
expect(typeof pp.pc.setRemoteDescription).toBe("function");
|
||||||
expect(pp.pc).not.toBeNull();
|
expect(pp.pc).not.toBeNull();
|
||||||
expect(pp.receiveWebRTCOffer({})).toBe(false);
|
expect(pp.receiveWebRTCOffer({})).toBe(false);
|
||||||
return expect(pp.receiveWebRTCOffer({
|
expect(pp.receiveWebRTCOffer({
|
||||||
type: 'answer'
|
type: 'answer'
|
||||||
})).toBe(false);
|
})).toBe(false);
|
||||||
});
|
});
|
||||||
return it('accepts valid offers', function() {
|
|
||||||
|
it('accepts valid offers', function() {
|
||||||
expect(pp.pc).not.toBeNull();
|
expect(pp.pc).not.toBeNull();
|
||||||
return expect(pp.receiveWebRTCOffer({
|
expect(pp.receiveWebRTCOffer({
|
||||||
type: 'offer',
|
type: 'offer',
|
||||||
sdp: 'foo'
|
sdp: 'foo'
|
||||||
})).toBe(true);
|
})).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('responds with a WebRTC answer correctly', function() {
|
it('responds with a WebRTC answer correctly', function() {
|
||||||
spyOn(snowflake.broker, 'sendAnswer');
|
spyOn(snowflake.broker, 'sendAnswer');
|
||||||
pp.pc.onicecandidate({
|
pp.pc.onicecandidate({
|
||||||
candidate: null
|
candidate: null
|
||||||
});
|
});
|
||||||
return expect(snowflake.broker.sendAnswer).toHaveBeenCalled();
|
expect(snowflake.broker.sendAnswer).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles a new data channel correctly', function() {
|
it('handles a new data channel correctly', function() {
|
||||||
expect(pp.client).toBeNull();
|
expect(pp.client).toBeNull();
|
||||||
pp.pc.ondatachannel({
|
pp.pc.ondatachannel({
|
||||||
|
@ -84,21 +93,25 @@ describe('ProxyPair', function() {
|
||||||
expect(pp.client.onopen).not.toBeNull();
|
expect(pp.client.onopen).not.toBeNull();
|
||||||
expect(pp.client.onclose).not.toBeNull();
|
expect(pp.client.onclose).not.toBeNull();
|
||||||
expect(pp.client.onerror).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() {
|
it('connects to the relay once datachannel opens', function() {
|
||||||
spyOn(pp, 'connectRelay');
|
spyOn(pp, 'connectRelay');
|
||||||
pp.client.onopen();
|
pp.client.onopen();
|
||||||
return expect(pp.connectRelay).toHaveBeenCalled();
|
expect(pp.connectRelay).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('connects to a relay', function() {
|
it('connects to a relay', function() {
|
||||||
pp.connectRelay();
|
pp.connectRelay();
|
||||||
expect(pp.relay.onopen).not.toBeNull();
|
expect(pp.relay.onopen).not.toBeNull();
|
||||||
expect(pp.relay.onclose).not.toBeNull();
|
expect(pp.relay.onclose).not.toBeNull();
|
||||||
expect(pp.relay.onerror).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() {
|
it('proxies data from client to relay', function() {
|
||||||
var msg;
|
var msg;
|
||||||
pp.pc.ondatachannel({
|
pp.pc.ondatachannel({
|
||||||
|
@ -116,8 +129,9 @@ describe('ProxyPair', function() {
|
||||||
pp.onClientToRelayMessage(msg);
|
pp.onClientToRelayMessage(msg);
|
||||||
pp.flush();
|
pp.flush();
|
||||||
expect(pp.client.send).not.toHaveBeenCalled();
|
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() {
|
it('proxies data from relay to client', function() {
|
||||||
var msg;
|
var msg;
|
||||||
spyOn(pp.client, 'send');
|
spyOn(pp.client, 'send');
|
||||||
|
@ -128,16 +142,19 @@ describe('ProxyPair', function() {
|
||||||
pp.onRelayToClientMessage(msg);
|
pp.onRelayToClientMessage(msg);
|
||||||
pp.flush();
|
pp.flush();
|
||||||
expect(pp.client.send).toHaveBeenCalledWith(arrayMatching([4, 5, 6]));
|
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.client, 'send');
|
||||||
spyOn(pp.relay, 'send');
|
spyOn(pp.relay, 'send');
|
||||||
pp.flush();
|
pp.flush();
|
||||||
expect(pp.client.send).not.toHaveBeenCalled();
|
expect(pp.client.send).not.toHaveBeenCalled();
|
||||||
return expect(pp.relay.send).not.toHaveBeenCalled();
|
expect(pp.relay.send).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: rate limit tests
|
// TODO: rate limit tests
|
||||||
|
|
|
@ -1,62 +1,43 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake
|
jasmine tests for Snowflake
|
||||||
*/
|
*/
|
||||||
var FakeBroker, PeerConnection, SessionDescription, WebSocket, config, log, ui;
|
|
||||||
|
|
||||||
// Fake browser functionality:
|
// Fake browser functionality:
|
||||||
PeerConnection = class PeerConnection {
|
class PeerConnection {
|
||||||
setRemoteDescription() {
|
setRemoteDescription() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(data) {}
|
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() {
|
var ui = new UI;
|
||||||
class WebSocket {
|
|
||||||
constructor() {
|
|
||||||
this.bufferedAmount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data) {}
|
class FakeBroker {
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
WebSocket.prototype.OPEN = 1;
|
|
||||||
|
|
||||||
WebSocket.prototype.CLOSED = 0;
|
|
||||||
|
|
||||||
return WebSocket;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
log = function() {};
|
|
||||||
|
|
||||||
config = new Config;
|
|
||||||
|
|
||||||
ui = new UI;
|
|
||||||
|
|
||||||
FakeBroker = class FakeBroker {
|
|
||||||
getClientOffer() {
|
getClientOffer() {
|
||||||
return new Promise(function(F, R) {
|
return new Promise(function(F, R) {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Snowflake', function() {
|
describe('Snowflake', function() {
|
||||||
|
|
||||||
it('constructs correctly', function() {
|
it('constructs correctly', function() {
|
||||||
var s;
|
var s;
|
||||||
s = new Snowflake(config, ui, {
|
s = new Snowflake(config, ui, {
|
||||||
|
@ -67,22 +48,25 @@ describe('Snowflake', function() {
|
||||||
fake: 'broker'
|
fake: 'broker'
|
||||||
});
|
});
|
||||||
expect(s.ui).not.toBeNull();
|
expect(s.ui).not.toBeNull();
|
||||||
return expect(s.retries).toBe(0);
|
expect(s.retries).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets relay address correctly', function() {
|
it('sets relay address correctly', function() {
|
||||||
var s;
|
var s;
|
||||||
s = new Snowflake(config, ui, null);
|
s = new Snowflake(config, ui, null);
|
||||||
s.setRelayAddr('foo');
|
s.setRelayAddr('foo');
|
||||||
return expect(s.relayAddr).toEqual('foo');
|
expect(s.relayAddr).toEqual('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('initalizes WebRTC connection', function() {
|
it('initalizes WebRTC connection', function() {
|
||||||
var s;
|
var s;
|
||||||
s = new Snowflake(config, ui, new FakeBroker());
|
s = new Snowflake(config, ui, new FakeBroker());
|
||||||
spyOn(s.broker, 'getClientOffer').and.callThrough();
|
spyOn(s.broker, 'getClientOffer').and.callThrough();
|
||||||
s.beginWebRTC();
|
s.beginWebRTC();
|
||||||
expect(s.retries).toBe(1);
|
expect(s.retries).toBe(1);
|
||||||
return expect(s.broker.getClientOffer).toHaveBeenCalled();
|
expect(s.broker.getClientOffer).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('receives SDP offer and sends answer', function() {
|
it('receives SDP offer and sends answer', function() {
|
||||||
var pair, s;
|
var pair, s;
|
||||||
s = new Snowflake(config, ui, new FakeBroker());
|
s = new Snowflake(config, ui, new FakeBroker());
|
||||||
|
@ -92,8 +76,9 @@ describe('Snowflake', function() {
|
||||||
spyOn(pair, 'receiveWebRTCOffer').and.returnValue(true);
|
spyOn(pair, 'receiveWebRTCOffer').and.returnValue(true);
|
||||||
spyOn(s, 'sendAnswer');
|
spyOn(s, 'sendAnswer');
|
||||||
s.receiveOffer(pair, '{"type":"offer","sdp":"foo"}');
|
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() {
|
it('does not send answer when receiving invalid offer', function() {
|
||||||
var pair, s;
|
var pair, s;
|
||||||
s = new Snowflake(config, ui, new FakeBroker());
|
s = new Snowflake(config, ui, new FakeBroker());
|
||||||
|
@ -103,12 +88,14 @@ describe('Snowflake', function() {
|
||||||
spyOn(pair, 'receiveWebRTCOffer').and.returnValue(false);
|
spyOn(pair, 'receiveWebRTCOffer').and.returnValue(false);
|
||||||
spyOn(s, 'sendAnswer');
|
spyOn(s, 'sendAnswer');
|
||||||
s.receiveOffer(pair, '{"type":"not a good offer","sdp":"foo"}');
|
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;
|
var s;
|
||||||
s = new Snowflake(config, ui, new FakeBroker());
|
s = new Snowflake(config, ui, new FakeBroker());
|
||||||
s.makeProxyPair();
|
s.makeProxyPair();
|
||||||
return expect(s.proxyPairs.length).toBe(1);
|
expect(s.proxyPairs.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake UI
|
jasmine tests for Snowflake UI
|
||||||
*/
|
*/
|
||||||
var document;
|
var document = {
|
||||||
|
|
||||||
document = {
|
|
||||||
getElementById: function(id) {
|
getElementById: function(id) {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
@ -14,6 +11,7 @@ document = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('UI', function() {
|
describe('UI', function() {
|
||||||
|
|
||||||
it('activates debug mode when badge does not exist', function() {
|
it('activates debug mode when badge does not exist', function() {
|
||||||
var u;
|
var u;
|
||||||
spyOn(document, 'getElementById').and.callFake(function(id) {
|
spyOn(document, 'getElementById').and.callFake(function(id) {
|
||||||
|
@ -25,8 +23,9 @@ describe('UI', function() {
|
||||||
u = new DebugUI();
|
u = new DebugUI();
|
||||||
expect(document.getElementById.calls.count()).toEqual(2);
|
expect(document.getElementById.calls.count()).toEqual(2);
|
||||||
expect(u.$status).not.toBeNull();
|
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() {
|
it('is not debug mode when badge exists', function() {
|
||||||
var u;
|
var u;
|
||||||
spyOn(document, 'getElementById').and.callFake(function(id) {
|
spyOn(document, 'getElementById').and.callFake(function(id) {
|
||||||
|
@ -38,8 +37,9 @@ describe('UI', function() {
|
||||||
u = new BadgeUI();
|
u = new BadgeUI();
|
||||||
expect(document.getElementById).toHaveBeenCalled();
|
expect(document.getElementById).toHaveBeenCalled();
|
||||||
expect(document.getElementById.calls.count()).toEqual(1);
|
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() {
|
it('sets status message when in debug mode', function() {
|
||||||
var u;
|
var u;
|
||||||
u = new DebugUI();
|
u = new DebugUI();
|
||||||
|
@ -50,16 +50,18 @@ describe('UI', function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
u.setStatus('test');
|
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() {
|
it('sets message log css correctly for debug mode', function() {
|
||||||
var u;
|
var u;
|
||||||
u = new DebugUI();
|
u = new DebugUI();
|
||||||
u.setActive(true);
|
u.setActive(true);
|
||||||
expect(u.$msglog.className).toEqual('active');
|
expect(u.$msglog.className).toEqual('active');
|
||||||
u.setActive(false);
|
u.setActive(false);
|
||||||
return expect(u.$msglog.className).toEqual('');
|
expect(u.$msglog.className).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets badge css correctly for non-debug mode', function() {
|
it('sets badge css correctly for non-debug mode', function() {
|
||||||
var u;
|
var u;
|
||||||
u = new BadgeUI();
|
u = new BadgeUI();
|
||||||
|
@ -67,9 +69,10 @@ describe('UI', function() {
|
||||||
u.setActive(true);
|
u.setActive(true);
|
||||||
expect(u.$badge.className).toEqual('active');
|
expect(u.$badge.className).toEqual('active');
|
||||||
u.setActive(false);
|
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;
|
var u;
|
||||||
u = new DebugUI();
|
u = new DebugUI();
|
||||||
u.$msglog = {
|
u.$msglog = {
|
||||||
|
@ -79,6 +82,7 @@ describe('UI', function() {
|
||||||
};
|
};
|
||||||
u.log('test');
|
u.log('test');
|
||||||
expect(u.$msglog.value).toEqual('test\n');
|
expect(u.$msglog.value).toEqual('test\n');
|
||||||
return expect(u.$msglog.scrollTop).toEqual(1337);
|
expect(u.$msglog.scrollTop).toEqual(1337);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake utils
|
jasmine tests for Snowflake utils
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe('Parse', function() {
|
describe('Parse', function() {
|
||||||
|
|
||||||
describe('cookie', function() {
|
describe('cookie', function() {
|
||||||
return it('parses correctly', function() {
|
|
||||||
|
it('parses correctly', function() {
|
||||||
expect(Parse.cookie('')).toEqual({});
|
expect(Parse.cookie('')).toEqual({});
|
||||||
expect(Parse.cookie('a=b')).toEqual({
|
expect(Parse.cookie('a=b')).toEqual({
|
||||||
a: 'b'
|
a: 'b'
|
||||||
|
@ -30,12 +32,15 @@ describe('Parse', function() {
|
||||||
expect(Parse.cookie('key=%26%20')).toEqual({
|
expect(Parse.cookie('key=%26%20')).toEqual({
|
||||||
key: '& '
|
key: '& '
|
||||||
});
|
});
|
||||||
return expect(Parse.cookie('a=\'\'')).toEqual({
|
expect(Parse.cookie('a=\'\'')).toEqual({
|
||||||
a: '\'\''
|
a: '\'\''
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('address', function() {
|
describe('address', function() {
|
||||||
|
|
||||||
it('parses IPv4', function() {
|
it('parses IPv4', function() {
|
||||||
expect(Parse.address('')).toBeNull();
|
expect(Parse.address('')).toBeNull();
|
||||||
expect(Parse.address('3.3.3.3:4444')).toEqual({
|
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')).toBeNull();
|
||||||
expect(Parse.address('3.3.3.3:0x1111')).toBeNull();
|
expect(Parse.address('3.3.3.3:0x1111')).toBeNull();
|
||||||
expect(Parse.address('3.3.3.3:-4444')).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({
|
expect(Parse.address('[1:2::a:f]:4444')).toEqual({
|
||||||
host: '1:2::a:f',
|
host: '1:2::a:f',
|
||||||
port: 4444
|
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]:0x1111')).toBeNull();
|
||||||
expect(Parse.address('[1:2::a:f]:-4444')).toBeNull();
|
expect(Parse.address('[1:2::a:f]:-4444')).toBeNull();
|
||||||
expect(Parse.address('[1:2::a:f]:65536')).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',
|
host: '1:2::ffff:1.2.3.4',
|
||||||
port: 4444
|
port: 4444
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
return describe('ipFromSDP', function() {
|
|
||||||
var testCases;
|
describe('ipFromSDP', function() {
|
||||||
testCases = [
|
|
||||||
|
var testCases = [
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5
|
// 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",
|
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
|
expected: void 0
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
return it('parses SDP', function() {
|
|
||||||
|
it('parses SDP', function() {
|
||||||
var i, len, ref, ref1, results, test;
|
var i, len, ref, ref1, results, test;
|
||||||
results = [];
|
results = [];
|
||||||
for (i = 0, len = testCases.length; i < len; i++) {
|
for (i = 0, len = testCases.length; i < len; i++) {
|
||||||
|
@ -143,10 +152,13 @@ describe('Parse', function() {
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('query string', function() {
|
describe('query string', function() {
|
||||||
|
|
||||||
it('should parse correctly', function() {
|
it('should parse correctly', function() {
|
||||||
expect(Query.parse('')).toEqual({});
|
expect(Query.parse('')).toEqual({});
|
||||||
expect(Query.parse('a=b')).toEqual({
|
expect(Query.parse('a=b')).toEqual({
|
||||||
|
@ -178,11 +190,12 @@ describe('query string', function() {
|
||||||
expect(Query.parse('a+b=c')).toEqual({
|
expect(Query.parse('a+b=c')).toEqual({
|
||||||
'a b': 'c'
|
'a b': 'c'
|
||||||
});
|
});
|
||||||
return expect(Query.parse('a=b+c+d')).toEqual({
|
expect(Query.parse('a=b+c+d')).toEqual({
|
||||||
a: 'b c d'
|
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({
|
expect(Query.parse('a=b&c=d&a=e')).toEqual({
|
||||||
a: 'b',
|
a: 'b',
|
||||||
c: 'd'
|
c: 'd'
|
||||||
|
@ -201,21 +214,24 @@ describe('query string', function() {
|
||||||
a: 'b',
|
a: 'b',
|
||||||
'': ''
|
'': ''
|
||||||
});
|
});
|
||||||
return expect(Query.parse('a=b&&c=d')).toEqual({
|
expect(Query.parse('a=b&&c=d')).toEqual({
|
||||||
a: 'b',
|
a: 'b',
|
||||||
'': '',
|
'': '',
|
||||||
c: 'd'
|
c: 'd'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Params', function() {
|
describe('Params', function() {
|
||||||
|
|
||||||
describe('bool', function() {
|
describe('bool', function() {
|
||||||
var getBool;
|
|
||||||
getBool = function(query) {
|
var getBool = function(query) {
|
||||||
return Params.getBool(Query.parse(query), 'param', false);
|
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=true')).toBe(true);
|
||||||
expect(getBool('param')).toBe(true);
|
expect(getBool('param')).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=0')).toBe(false);
|
||||||
expect(getBool('param=false')).toBe(false);
|
expect(getBool('param=false')).toBe(false);
|
||||||
expect(getBool('param=unexpected')).toBeNull();
|
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;
|
describe('address', function() {
|
||||||
DEFAULT = {
|
|
||||||
|
var DEFAULT = {
|
||||||
host: '1.1.1.1',
|
host: '1.1.1.1',
|
||||||
port: 2222
|
port: 2222
|
||||||
};
|
};
|
||||||
getAddress = function(query) {
|
|
||||||
|
var getAddress = function(query) {
|
||||||
return Params.getAddress(query, 'addr', DEFAULT);
|
return Params.getAddress(query, 'addr', DEFAULT);
|
||||||
};
|
};
|
||||||
return it('parses correctly', function() {
|
|
||||||
|
it('parses correctly', function() {
|
||||||
expect(getAddress({})).toEqual(DEFAULT);
|
expect(getAddress({})).toEqual(DEFAULT);
|
||||||
expect(getAddress({
|
expect(getAddress({
|
||||||
addr: '3.3.3.3:4444'
|
addr: '3.3.3.3:4444'
|
||||||
|
@ -246,9 +266,11 @@ describe('Params', function() {
|
||||||
expect(getAddress({
|
expect(getAddress({
|
||||||
x: '3.3.3.3:4444'
|
x: '3.3.3.3:4444'
|
||||||
})).toEqual(DEFAULT);
|
})).toEqual(DEFAULT);
|
||||||
return expect(getAddress({
|
expect(getAddress({
|
||||||
addr: '---'
|
addr: '---'
|
||||||
})).toBeNull();
|
})).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,32 +1,39 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
jasmine tests for Snowflake websocket
|
jasmine tests for Snowflake websocket
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe('BuildUrl', function() {
|
describe('BuildUrl', function() {
|
||||||
|
|
||||||
it('should parse just protocol and host', 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() {
|
it('should handle different ports', function() {
|
||||||
expect(WS.buildUrl('http', 'example.com', 80)).toBe('http://example.com');
|
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', 81)).toBe('http://example.com:81');
|
||||||
expect(WS.buildUrl('http', 'example.com', 443)).toBe('http://example.com:443');
|
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() {
|
it('should handle paths', function() {
|
||||||
expect(WS.buildUrl('http', 'example.com', 80, '/')).toBe('http://example.com/');
|
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');
|
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() {
|
it('should handle params', function() {
|
||||||
expect(WS.buildUrl('http', 'example.com', 80, '/test', [['k', '%#v']])).toBe('http://example.com/test?k=%25%23v');
|
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() {
|
it('should handle ips', function() {
|
||||||
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 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');
|
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
323
proxy/ui.js
323
proxy/ui.js
|
@ -1,197 +1,178 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
/*
|
||||||
/*
|
All of Snowflake's DOM manipulation and inputs.
|
||||||
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 {
|
||||||
class UI {
|
|
||||||
setStatus(msg) {}
|
|
||||||
|
|
||||||
setActive(connected) {
|
setStatus(msg) {}
|
||||||
return this.active = connected;
|
|
||||||
|
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 WebExtUI extends UI {
|
||||||
class BadgeUI extends UI {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.$badge = document.getElementById('badge');
|
|
||||||
}
|
|
||||||
|
|
||||||
setActive(connected) {
|
constructor() {
|
||||||
super.setActive(connected);
|
super();
|
||||||
return this.$badge.className = connected ? 'active' : '';
|
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];
|
||||||
BadgeUI.prototype.$badge = null;
|
return setInterval((() => {
|
||||||
|
this.stats.unshift(0);
|
||||||
return BadgeUI;
|
this.stats.splice(24);
|
||||||
|
|
||||||
}).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);
|
|
||||||
return this.postActive();
|
return this.postActive();
|
||||||
}
|
}), 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
onMessage(m) {
|
initToggle() {
|
||||||
var storing;
|
var getting;
|
||||||
boundMethodCheck(this, WebExtUI);
|
return getting = chrome.storage.local.get("snowflake-enabled", (result) => {
|
||||||
this.enabled = m.enabled;
|
if (result['snowflake-enabled'] !== void 0) {
|
||||||
this.setEnabled(this.enabled);
|
this.enabled = result['snowflake-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"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return chrome.browserAction.setIcon({
|
log("Toggle state not yet saved");
|
||||||
path: {
|
|
||||||
32: "icons/status-on.png"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
return this.setEnabled(this.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setEnabled(enabled) {
|
postActive() {
|
||||||
update();
|
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({
|
return chrome.browserAction.setIcon({
|
||||||
path: {
|
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;
|
WebExtUI.prototype.stats = null;
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
154
proxy/util.js
154
proxy/util.js
|
@ -1,53 +1,54 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
A Coffeescript WebRTC snowflake proxy
|
A Coffeescript WebRTC snowflake proxy
|
||||||
|
|
||||||
Contains helpers for parsing query strings and other utilities.
|
Contains helpers for parsing query strings and other utilities.
|
||||||
*/
|
*/
|
||||||
var BucketRateLimit, DummyRateLimit, Params, Parse, Query, Util;
|
|
||||||
|
|
||||||
Util = (function() {
|
class Util {
|
||||||
class Util {
|
|
||||||
static mightBeTBB() {
|
static mightBeTBB() {
|
||||||
return Util.TBB_UAS.indexOf(window.navigator.userAgent) > -1 && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
|
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;
|
||||||
}
|
}
|
||||||
|
// Also do nothing if running in Tor Browser.
|
||||||
static genSnowflakeID() {
|
if (Util.mightBeTBB()) {
|
||||||
return Math.random().toString(36).substring(2);
|
log('Will not run within Tor Browser.');
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static snowflakeIsDisabled(cookieName) {
|
static featureDetect() {
|
||||||
var cookies;
|
return typeof PeerConnection === 'function';
|
||||||
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;
|
class Query {
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
Query = class Query {
|
|
||||||
/*
|
/*
|
||||||
Parse a URL query string or application/x-www-form-urlencoded body. The
|
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,
|
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
|
// Parse a cookie data string (usually document.cookie). The return type is an
|
||||||
// object mapping cookies names to values. Returns null on error.
|
// object mapping cookies names to values. Returns null on error.
|
||||||
// http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-8747038
|
// 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) {
|
static getBool(query, param, defaultValue) {
|
||||||
var val;
|
var val;
|
||||||
val = query[param];
|
val = query[param];
|
||||||
|
@ -254,53 +259,52 @@ Params = class Params {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BucketRateLimit = (function() {
|
|
||||||
class BucketRateLimit {
|
class BucketRateLimit {
|
||||||
constructor(capacity, time) {
|
|
||||||
this.capacity = capacity;
|
constructor(capacity, time) {
|
||||||
this.time = 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() {
|
update(n) {
|
||||||
var delta, now;
|
this.age();
|
||||||
now = new Date();
|
this.amount += n;
|
||||||
delta = (now - this.lastUpdate) / 1000.0;
|
return this.amount <= this.capacity;
|
||||||
this.lastUpdate = now;
|
}
|
||||||
this.amount -= delta * this.capacity / this.time;
|
|
||||||
if (this.amount < 0.0) {
|
|
||||||
return this.amount = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update(n) {
|
// How many seconds in the future will the limit expire?
|
||||||
this.age();
|
when() {
|
||||||
this.amount += n;
|
this.age();
|
||||||
return this.amount <= this.capacity;
|
return (this.amount - this.capacity) / (this.capacity / this.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// How many seconds in the future will the limit expire?
|
isLimited() {
|
||||||
when() {
|
this.age();
|
||||||
this.age();
|
return this.amount > this.capacity;
|
||||||
return (this.amount - this.capacity) / (this.capacity / this.time);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
// A rate limiter that never limits.
|
||||||
DummyRateLimit = class DummyRateLimit {
|
class DummyRateLimit {
|
||||||
|
|
||||||
constructor(capacity, time) {
|
constructor(capacity, time) {
|
||||||
this.capacity = capacity;
|
this.capacity = capacity;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
|
|
|
@ -1,70 +1,64 @@
|
||||||
// Generated by CoffeeScript 2.4.1
|
|
||||||
/*
|
/*
|
||||||
Only websocket-specific stuff.
|
Only websocket-specific stuff.
|
||||||
*/
|
*/
|
||||||
var WS;
|
|
||||||
|
|
||||||
WS = (function() {
|
class WS {
|
||||||
class WS {
|
|
||||||
// Build an escaped URL string from unescaped components. Only scheme and host
|
// Build an escaped URL string from unescaped components. Only scheme and host
|
||||||
// are required. See RFC 3986, section 3.
|
// are required. See RFC 3986, section 3.
|
||||||
static buildUrl(scheme, host, port, path, params) {
|
static buildUrl(scheme, host, port, path, params) {
|
||||||
var parts;
|
var parts;
|
||||||
parts = [];
|
parts = [];
|
||||||
parts.push(encodeURIComponent(scheme));
|
parts.push(encodeURIComponent(scheme));
|
||||||
parts.push('://');
|
parts.push('://');
|
||||||
// If it contains a colon but no square brackets, treat it as IPv6.
|
// If it contains a colon but no square brackets, treat it as IPv6.
|
||||||
if (host.match(/:/) && !host.match(/[[\]]/)) {
|
if (host.match(/:/) && !host.match(/[[\]]/)) {
|
||||||
parts.push('[');
|
parts.push('[');
|
||||||
parts.push(host);
|
parts.push(host);
|
||||||
parts.push(']');
|
parts.push(']');
|
||||||
} else {
|
} else {
|
||||||
parts.push(encodeURIComponent(host));
|
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('');
|
|
||||||
}
|
}
|
||||||
|
if (void 0 !== port && this.DEFAULT_PORTS[scheme] !== port) {
|
||||||
static makeWebsocket(addr, params) {
|
parts.push(':');
|
||||||
var url, ws, wsProtocol;
|
parts.push(encodeURIComponent(port.toString()));
|
||||||
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 !== 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 = {
|
WS.WSS_ENABLED = true;
|
||||||
http: 80,
|
|
||||||
https: 443
|
|
||||||
};
|
|
||||||
|
|
||||||
return WS;
|
WS.DEFAULT_PORTS = {
|
||||||
|
http: 80,
|
||||||
}).call(this);
|
https: 443
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue