Reimagine the badge
Trac 27385
4
.gitignore
vendored
|
@ -15,5 +15,9 @@ proxy/build
|
||||||
proxy/node_modules
|
proxy/node_modules
|
||||||
proxy/spec/support
|
proxy/spec/support
|
||||||
proxy/webext/snowflake.js
|
proxy/webext/snowflake.js
|
||||||
|
proxy/webext/popup.js
|
||||||
|
proxy/webext/embed.html
|
||||||
|
proxy/webext/embed.css
|
||||||
|
proxy/webext/icons/
|
||||||
ignore/
|
ignore/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
|
@ -1,18 +1,71 @@
|
||||||
/* global TESTING, Util, Params, Config, DebugUI, BadgeUI, UI, Broker, Snowflake */
|
/* global TESTING, Util, Params, Config, DebugUI, BadgeUI, UI, Broker, Snowflake */
|
||||||
|
|
||||||
|
/*
|
||||||
|
UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BadgeUI extends UI {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.popup = new Popup();
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus() {}
|
||||||
|
|
||||||
|
missingFeature(missing) {
|
||||||
|
this.popup.setImgSrc('off');
|
||||||
|
this.popup.setStatusText("Snowflake is off");
|
||||||
|
this.popup.setStatusDesc(missing, 'firebrick');
|
||||||
|
this.popup.hideButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
turnOn() {
|
||||||
|
const clients = this.active ? 1 : 0;
|
||||||
|
this.popup.setChecked(true);
|
||||||
|
this.popup.setToggleText('Turn Off');
|
||||||
|
this.popup.setStatusText(`${clients} client${(clients !== 1) ? 's' : ''} connected.`);
|
||||||
|
// FIXME: Share stats from webext
|
||||||
|
const total = 0;
|
||||||
|
this.popup.setStatusDesc(`Your snowflake has helped ${total} user${(total !== 1) ? 's' : ''} circumvent censorship in the last 24 hours.`);
|
||||||
|
this.popup.setImgSrc(this.active ? "running" : "on");
|
||||||
|
}
|
||||||
|
|
||||||
|
turnOff() {
|
||||||
|
this.popup.setChecked(false);
|
||||||
|
this.popup.setToggleText('Turn On');
|
||||||
|
this.popup.setStatusText("Snowflake is off");
|
||||||
|
this.popup.setStatusDesc("");
|
||||||
|
this.popup.setImgSrc("off");
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive(connected) {
|
||||||
|
super.setActive(connected);
|
||||||
|
turnOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BadgeUI.prototype.popup = null;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Entry point.
|
Entry point.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var snowflake, query, debug, silenceNotifications, log, dbg, init;
|
// Defaults to opt-in.
|
||||||
|
var COOKIE_NAME = "snowflake-allow";
|
||||||
|
var COOKIE_LIFETIME = "Thu, 01 Jan 2038 00:00:00 GMT";
|
||||||
|
var COOKIE_EXPIRE = "Thu, 01 Jan 1970 00:00:01 GMT";
|
||||||
|
|
||||||
|
function setSnowflakeCookie(val, expires) {
|
||||||
|
document.cookie = `${COOKIE_NAME}=${val}; path=/; expires=${expires};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
var debug, snowflake, config, broker, ui, log, dbg, init, update, silenceNotifications, query;
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (((typeof TESTING === "undefined" || TESTING === null) || !TESTING) && !Util.featureDetect()) {
|
|
||||||
console.log('webrtc feature not detected. shutting down');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
snowflake = null;
|
snowflake = null;
|
||||||
|
|
||||||
query = new URLSearchParams(location.search);
|
query = new URLSearchParams(location.search);
|
||||||
|
@ -35,32 +88,56 @@ var snowflake, query, debug, silenceNotifications, log, dbg, init;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
update = function() {
|
||||||
|
const cookies = Parse.cookie(document.cookie);
|
||||||
|
if (cookies[COOKIE_NAME] === '1') {
|
||||||
|
ui.turnOn();
|
||||||
|
dbg('Contacting Broker at ' + broker.url);
|
||||||
|
log('Starting snowflake');
|
||||||
|
snowflake.setRelayAddr(config.relayAddr);
|
||||||
|
snowflake.beginWebRTC();
|
||||||
|
} else {
|
||||||
|
ui.turnOff();
|
||||||
|
snowflake.disable();
|
||||||
|
log('Currently not active.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
init = function() {
|
init = function() {
|
||||||
var broker, config, ui;
|
ui = new BadgeUI();
|
||||||
|
|
||||||
|
if (!Util.hasWebRTC()) {
|
||||||
|
ui.missingFeature("WebRTC feature is not detected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Util.hasCookies()) {
|
||||||
|
ui.missingFeature("Cookies are not enabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Util.mightBeTBB()) {
|
||||||
|
ui.missingFeature("Will not run within Tor Browser.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
config = new Config;
|
config = new Config;
|
||||||
if ('off' !== query.get('ratelimit')) {
|
if ('off' !== query.get('ratelimit')) {
|
||||||
config.rateLimitBytes = Params.getByteCount(query, 'ratelimit', config.rateLimitBytes);
|
config.rateLimitBytes = Params.getByteCount(query, 'ratelimit', config.rateLimitBytes);
|
||||||
}
|
}
|
||||||
ui = null;
|
|
||||||
if (document.getElementById('badge') !== null) {
|
|
||||||
ui = new BadgeUI();
|
|
||||||
} else if (document.getElementById('status') !== null) {
|
|
||||||
ui = new DebugUI();
|
|
||||||
} else {
|
|
||||||
ui = new UI();
|
|
||||||
}
|
|
||||||
broker = new Broker(config.brokerUrl);
|
broker = new Broker(config.brokerUrl);
|
||||||
snowflake = new Snowflake(config, ui, broker);
|
snowflake = new Snowflake(config, ui, broker);
|
||||||
log('== snowflake proxy ==');
|
log('== snowflake proxy ==');
|
||||||
if (Util.snowflakeIsDisabled(config.cookieName)) {
|
update();
|
||||||
// Do not activate the proxy if any number of conditions are true.
|
|
||||||
log('Currently not active.');
|
document.getElementById('enabled').addEventListener('change', (event) => {
|
||||||
return;
|
if (event.target.checked) {
|
||||||
}
|
setSnowflakeCookie('1', COOKIE_LIFETIME);
|
||||||
// Otherwise, begin setting up WebRTC and acting as a proxy.
|
} else {
|
||||||
dbg('Contacting Broker at ' + broker.url);
|
setSnowflakeCookie('', COOKIE_EXPIRE);
|
||||||
snowflake.setRelayAddr(config.relayAddr);
|
}
|
||||||
return snowflake.beginWebRTC();
|
update();
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Notification of closing tab with active proxy.
|
// Notification of closing tab with active proxy.
|
||||||
|
|
83
proxy/init-testing.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/* global TESTING, Util, Params, Config, DebugUI, UI, Broker, Snowflake */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Entry point.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var snowflake, query, debug, silenceNotifications, log, dbg, init;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
if (((typeof TESTING === "undefined" || TESTING === null) || !TESTING) && !Util.featureDetect()) {
|
||||||
|
console.log('webrtc feature not detected. shutting down');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snowflake = null;
|
||||||
|
|
||||||
|
query = new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
debug = Params.getBool(query, 'debug', false);
|
||||||
|
|
||||||
|
silenceNotifications = Params.getBool(query, 'silent', false);
|
||||||
|
|
||||||
|
// Log to both console and UI if applicable.
|
||||||
|
// Requires that the snowflake and UI objects are hooked up in order to
|
||||||
|
// log to console.
|
||||||
|
log = function(msg) {
|
||||||
|
console.log('Snowflake: ' + msg);
|
||||||
|
return snowflake != null ? snowflake.ui.log(msg) : void 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg = function(msg) {
|
||||||
|
if (debug || ((snowflake != null ? snowflake.ui : void 0) instanceof DebugUI)) {
|
||||||
|
return log(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
init = function() {
|
||||||
|
var broker, config, ui;
|
||||||
|
config = new Config;
|
||||||
|
if ('off' !== query['ratelimit']) {
|
||||||
|
config.rateLimitBytes = Params.getByteCount(query, 'ratelimit', config.rateLimitBytes);
|
||||||
|
}
|
||||||
|
ui = null;
|
||||||
|
if (document.getElementById('status') !== null) {
|
||||||
|
ui = new DebugUI();
|
||||||
|
} else {
|
||||||
|
ui = new UI();
|
||||||
|
}
|
||||||
|
broker = new Broker(config.brokerUrl);
|
||||||
|
snowflake = new Snowflake(config, ui, broker);
|
||||||
|
log('== snowflake proxy ==');
|
||||||
|
if (Util.snowflakeIsDisabled(config.cookieName)) {
|
||||||
|
// Do not activate the proxy if any number of conditions are true.
|
||||||
|
log('Currently not active.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise, begin setting up WebRTC and acting as a proxy.
|
||||||
|
dbg('Contacting Broker at ' + broker.url);
|
||||||
|
snowflake.setRelayAddr(config.relayAddr);
|
||||||
|
return snowflake.beginWebRTC();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notification of closing tab with active proxy.
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
if (
|
||||||
|
!silenceNotifications &&
|
||||||
|
snowflake !== null &&
|
||||||
|
Snowflake.MODE.WEBRTC_READY === snowflake.state
|
||||||
|
) {
|
||||||
|
return Snowflake.MESSAGE.CONFIRMATION;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onunload = function() {
|
||||||
|
if (snowflake !== null) { snowflake.disable(); }
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = init;
|
||||||
|
|
||||||
|
}());
|
|
@ -1,6 +1,110 @@
|
||||||
/* global Util, chrome, Config, WebExtUI, Broker, Snowflake */
|
/* global Util, chrome, Config, WebExtUI, Broker, Snowflake */
|
||||||
/* eslint no-unused-vars: 0 */
|
/* eslint no-unused-vars: 0 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
this.setEnabled(this.enabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
postActive() {
|
||||||
|
var ref;
|
||||||
|
return (ref = this.port) != null ? ref.postMessage({
|
||||||
|
active: this.active,
|
||||||
|
total: this.stats.reduce((function(t, c) {
|
||||||
|
return t + c;
|
||||||
|
}), 0),
|
||||||
|
enabled: this.enabled
|
||||||
|
}) : void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect(port) {
|
||||||
|
this.port = port;
|
||||||
|
port.onDisconnect.addListener(this.onDisconnect);
|
||||||
|
port.onMessage.addListener(this.onMessage);
|
||||||
|
return this.postActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(m) {
|
||||||
|
this.enabled = m.enabled;
|
||||||
|
this.setEnabled(this.enabled);
|
||||||
|
this.postActive();
|
||||||
|
chrome.storage.local.set({
|
||||||
|
"snowflake-enabled": this.enabled
|
||||||
|
}, function() {
|
||||||
|
log("Stored toggle state");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisconnect() {
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Entry point.
|
Entry point.
|
||||||
*/
|
*/
|
||||||
|
@ -30,7 +134,7 @@ var debug, snowflake, config, broker, ui, log, dbg, init, update, silenceNotific
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!Util.featureDetect()) {
|
if (!Util.hasWebRTC()) {
|
||||||
chrome.runtime.onConnect.addListener(function(port) {
|
chrome.runtime.onConnect.addListener(function(port) {
|
||||||
return port.postMessage({
|
return port.postMessage({
|
||||||
missingFeature: true
|
missingFeature: true
|
||||||
|
|
|
@ -26,14 +26,19 @@ var FILES_SPEC = [
|
||||||
'spec/websocket.spec.js'
|
'spec/websocket.spec.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
var OUTFILE = 'snowflake.js';
|
|
||||||
|
|
||||||
var STATIC = 'static';
|
var STATIC = 'static';
|
||||||
|
|
||||||
var concatJS = function(outDir, init) {
|
var SHARED_FILES = [
|
||||||
|
'embed.html',
|
||||||
|
'embed.css',
|
||||||
|
'popup.js',
|
||||||
|
'icons'
|
||||||
|
];
|
||||||
|
|
||||||
|
var concatJS = function(outDir, init, outFile) {
|
||||||
var files;
|
var files;
|
||||||
files = FILES.concat(`init-${init}.js`);
|
files = FILES.concat(`init-${init}.js`);
|
||||||
return exec(`cat ${files.join(' ')} > ${outDir}/${OUTFILE}`, function(err) {
|
return exec(`cat ${files.join(' ')} > ${outDir}/${outFile}`, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +58,7 @@ task('test', 'snowflake unit tests', function() {
|
||||||
exec('mkdir -p test');
|
exec('mkdir -p test');
|
||||||
exec('jasmine init >&-');
|
exec('jasmine init >&-');
|
||||||
// Simply concat all the files because we're not using node exports.
|
// Simply concat all the files because we're not using node exports.
|
||||||
jasmineFiles = FILES.concat('init-badge.js', FILES_SPEC);
|
jasmineFiles = FILES.concat('init-testing.js', FILES_SPEC);
|
||||||
outFile = 'test/bundle.spec.js';
|
outFile = 'test/bundle.spec.js';
|
||||||
exec('echo "TESTING = true" > ' + outFile);
|
exec('echo "TESTING = true" > ' + outFile);
|
||||||
exec('cat ' + jasmineFiles.join(' ') + ' | cat >> ' + outFile);
|
exec('cat ' + jasmineFiles.join(' ') + ' | cat >> ' + outFile);
|
||||||
|
@ -68,19 +73,20 @@ task('test', 'snowflake unit tests', function() {
|
||||||
task('build', 'build the snowflake proxy', function() {
|
task('build', 'build the snowflake proxy', function() {
|
||||||
exec('rm -r build');
|
exec('rm -r build');
|
||||||
exec('cp -r ' + STATIC + '/ build/');
|
exec('cp -r ' + STATIC + '/ build/');
|
||||||
concatJS('build', 'badge');
|
concatJS('build', 'badge', 'embed.js');
|
||||||
console.log('Snowflake prepared.');
|
console.log('Snowflake prepared.');
|
||||||
});
|
});
|
||||||
|
|
||||||
task('webext', 'build the webextension', function() {
|
task('webext', 'build the webextension', function() {
|
||||||
exec('mkdir -p webext');
|
exec('mkdir -p webext');
|
||||||
concatJS('webext', 'webext');
|
exec(`cp -r ${STATIC}/{${SHARED_FILES.join(',')}} webext/`);
|
||||||
|
concatJS('webext', 'webext', 'snowflake.js');
|
||||||
console.log('Webextension prepared.');
|
console.log('Webextension prepared.');
|
||||||
});
|
});
|
||||||
|
|
||||||
task('node', 'build the node binary', function() {
|
task('node', 'build the node binary', function() {
|
||||||
exec('mkdir -p build');
|
exec('mkdir -p build');
|
||||||
concatJS('build', 'node');
|
concatJS('build', 'node', 'snowflake.js');
|
||||||
console.log('Node prepared.');
|
console.log('Node prepared.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
<Files "embed.html">
|
<Files "embed.html">
|
||||||
Header always unset X-Frame-Options
|
Header always unset X-Frame-Options
|
||||||
</Files>
|
</Files>
|
||||||
Redirect permanent /options.html /index.html
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ body {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
font-size:12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#active {
|
#active {
|
||||||
|
@ -27,14 +27,13 @@ body {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
background-image: url(../icons/arrowhead-right-12.svg);
|
background-image: url('icons/arrowhead-right-12.svg');
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
opacity : 0.6;
|
opacity : 0.6;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Snowflake Status */
|
/* Snowflake Status */
|
||||||
|
@ -43,14 +42,12 @@ body {
|
||||||
-webkit-animation:spin 8s linear infinite;
|
-webkit-animation:spin 8s linear infinite;
|
||||||
-moz-animation:spin 8s linear infinite;
|
-moz-animation:spin 8s linear infinite;
|
||||||
animation:spin 8s linear infinite;
|
animation:spin 8s linear infinite;
|
||||||
|
|
||||||
fill: BlueViolet;
|
fill: BlueViolet;
|
||||||
}
|
}
|
||||||
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
|
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
|
||||||
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
|
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
|
||||||
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
|
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
|
||||||
|
|
||||||
|
|
||||||
/* Toggle */
|
/* Toggle */
|
||||||
|
|
||||||
.switch {
|
.switch {
|
|
@ -1,52 +1,28 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="refresh" content="86400">
|
<!-- This should be essentially be a no-opt in the popup -->
|
||||||
<script type="text/javascript" src="snowflake.js"></script>
|
<meta http-equiv="refresh" content="86400" />
|
||||||
<style>
|
<link rel="stylesheet" href="embed.css" />
|
||||||
* {
|
<script src="popup.js"></script>
|
||||||
box-sizing: border-box;
|
<script src="embed.js"></script>
|
||||||
}
|
</head>
|
||||||
body {
|
<body>
|
||||||
position: absolute; width: 100%; height: 100%;
|
<div id="active">
|
||||||
top: 0; margin: 0 auto; padding: 0;
|
<img src="icons/status-off.png" />
|
||||||
background-color: #424;
|
<p>Snowflake is off</p>
|
||||||
text-align: center; cursor: default;
|
<p></p>
|
||||||
}
|
|
||||||
#badge {
|
|
||||||
margin: auto; padding: 0;
|
|
||||||
width: 88px; height: 16px;
|
|
||||||
background-image: url('koch.jpg');
|
|
||||||
white-space: nowrap;
|
|
||||||
color: #000;
|
|
||||||
font-size: 12px; font-weight: 900;
|
|
||||||
font-variant: small-caps;
|
|
||||||
text-shadow: 0 0 5px #fef,
|
|
||||||
0 1px 2px #fef,
|
|
||||||
0 -1px 2px #fef,
|
|
||||||
1px 0px 3px #fef,
|
|
||||||
-1px 0px 3px #fef;
|
|
||||||
}
|
|
||||||
.active {
|
|
||||||
-webkit-animation: bgScroll 8s linear infinite;
|
|
||||||
animation: bgScroll 8s linear infinite;
|
|
||||||
}
|
|
||||||
@-webkit-keyframes bgScroll {
|
|
||||||
from {background-position: 0 -4%;}
|
|
||||||
to {background-position: 0 104%;}
|
|
||||||
}
|
|
||||||
@keyframes bgScroll {
|
|
||||||
from {background-position: 0 -4%;}
|
|
||||||
to {background-position: 0 104%;}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a target="_blank" href="index.html">
|
|
||||||
<div id="badge">
|
|
||||||
Internet Freedom
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
<div class="b button">
|
||||||
</body>
|
<label id="toggle" for="enabled">Turn On</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="enabled" type="checkbox" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="b learn">
|
||||||
|
<a target="_blank" href="https://snowflake.torproject.org/">Learn more</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 445 B |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
|
@ -76,3 +76,8 @@ h3 {
|
||||||
.diagram img, .screenshot img {
|
.diagram img, .screenshot img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
|
@ -77,6 +77,17 @@
|
||||||
Consider adding keywords <em>snowflake-webextension</em> or <em>snowflake-client</em>
|
Consider adding keywords <em>snowflake-webextension</em> or <em>snowflake-client</em>
|
||||||
to let us know how which part of the Snowflake system is experiencing
|
to let us know how which part of the Snowflake system is experiencing
|
||||||
problems.</p>
|
problems.</p>
|
||||||
|
|
||||||
|
<h3>EMBED</h3>
|
||||||
|
|
||||||
|
<p>It is now possible to embed the Snowflake badge on any website:</p>
|
||||||
|
|
||||||
|
<textarea readonly><iframe src="https://snowflake.torproject.org/embed.html" width="320px" height="200px" frameborder="0" scrolling="no"></iframe></textarea>
|
||||||
|
|
||||||
|
<p>Which looks like this:</p>
|
||||||
|
|
||||||
|
<iframe src="embed.html" width="320px" height="200px" frameborder="0" scrolling="no"></iframe>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Before Width: | Height: | Size: 129 KiB |
28
proxy/static/popup.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/* exported Popup */
|
||||||
|
|
||||||
|
class Popup {
|
||||||
|
constructor() {
|
||||||
|
this.div = document.getElementById('active');
|
||||||
|
this.ps = this.div.querySelectorAll('p');
|
||||||
|
this.img = this.div.querySelector('img');
|
||||||
|
}
|
||||||
|
setImgSrc(src) {
|
||||||
|
this.img.src = `icons/status-${src}.png`;
|
||||||
|
}
|
||||||
|
setStatusText(txt) {
|
||||||
|
this.ps[0].innerText = txt;
|
||||||
|
}
|
||||||
|
setStatusDesc(desc, color) {
|
||||||
|
this.ps[1].innerText = desc;
|
||||||
|
this.ps[1].style.color = color || 'black';
|
||||||
|
}
|
||||||
|
hideButton() {
|
||||||
|
document.querySelector('.button').style.display = 'none';
|
||||||
|
}
|
||||||
|
setChecked(checked) {
|
||||||
|
document.getElementById('enabled').checked = checked;
|
||||||
|
}
|
||||||
|
setToggleText(txt) {
|
||||||
|
document.getElementById('toggle').innerText = txt;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Snowflake</title>
|
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
|
||||||
<script type="text/javascript" src="snowflake.js"></script>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-transition: all 0.3s;
|
|
||||||
-moz-transition: all 0.3s;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%; height: 100%; top: 0; margin: 0 auto;
|
|
||||||
background-color: #424;
|
|
||||||
color: #000;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 24px;
|
|
||||||
font-family: monospace;
|
|
||||||
background-image: url('koch.jpg');
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
background-color: rgba(0,0,0,0.8);
|
|
||||||
color: #fff;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
.chatarea {
|
|
||||||
position: relative; border: none;
|
|
||||||
width: 50%; min-width: 40em;
|
|
||||||
padding: 0.5em; margin: auto;
|
|
||||||
}
|
|
||||||
.active { background-color: rgba(0,50,0,0.8); }
|
|
||||||
#msglog {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 40em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
#status {
|
|
||||||
background-color: rgba(0,0,0,0.9); color: #999;
|
|
||||||
margin: 8px 0; padding: 8px 1em; cursor: default;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="chatarea">
|
|
||||||
<div id="status">
|
|
||||||
Timeout...
|
|
||||||
</div>
|
|
||||||
<textarea id="msglog" readonly>
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
118
proxy/ui.js
|
@ -21,23 +21,6 @@ UI.prototype.active = false;
|
||||||
UI.prototype.enabled = true;
|
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 {
|
class DebugUI extends UI {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -75,104 +58,3 @@ class DebugUI extends UI {
|
||||||
DebugUI.prototype.$msglog = null;
|
DebugUI.prototype.$msglog = null;
|
||||||
|
|
||||||
DebugUI.prototype.$status = null;
|
DebugUI.prototype.$status = null;
|
||||||
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
this.setEnabled(this.enabled);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postActive() {
|
|
||||||
var ref;
|
|
||||||
return (ref = this.port) != null ? ref.postMessage({
|
|
||||||
active: this.active,
|
|
||||||
total: this.stats.reduce((function(t, c) {
|
|
||||||
return t + c;
|
|
||||||
}), 0),
|
|
||||||
enabled: this.enabled
|
|
||||||
}) : void 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnect(port) {
|
|
||||||
this.port = port;
|
|
||||||
port.onDisconnect.addListener(this.onDisconnect);
|
|
||||||
port.onMessage.addListener(this.onMessage);
|
|
||||||
return this.postActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(m) {
|
|
||||||
this.enabled = m.enabled;
|
|
||||||
this.setEnabled(this.enabled);
|
|
||||||
this.postActive();
|
|
||||||
chrome.storage.local.set({
|
|
||||||
"snowflake-enabled": this.enabled
|
|
||||||
}, function() {
|
|
||||||
log("Stored toggle state");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisconnect() {
|
|
||||||
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 {
|
|
||||||
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;
|
|
||||||
|
|
|
@ -10,31 +10,21 @@ Contains helpers for parsing query strings and other utilities.
|
||||||
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() {
|
static genSnowflakeID() {
|
||||||
return Math.random().toString(36).substring(2);
|
return Math.random().toString(36).substring(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static snowflakeIsDisabled(cookieName) {
|
static hasWebRTC() {
|
||||||
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() {
|
static hasCookies() {
|
||||||
return typeof PeerConnection === 'function';
|
return navigator.cookieEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,9 @@
|
||||||
/* global chrome */
|
/* global chrome, Popup */
|
||||||
|
|
||||||
const port = chrome.runtime.connect({
|
const port = chrome.runtime.connect({
|
||||||
name: "popup"
|
name: "popup"
|
||||||
});
|
});
|
||||||
|
|
||||||
class Popup {
|
|
||||||
constructor() {
|
|
||||||
this.div = document.getElementById('active');
|
|
||||||
this.ps = this.div.querySelectorAll('p');
|
|
||||||
this.img = this.div.querySelector('img');
|
|
||||||
}
|
|
||||||
setImgSrc(src) {
|
|
||||||
this.img.src = `icons/status-${src}.png`;
|
|
||||||
}
|
|
||||||
setStatusText(txt) {
|
|
||||||
this.ps[0].innerText = txt;
|
|
||||||
}
|
|
||||||
setStatusDesc(desc, color) {
|
|
||||||
this.ps[1].innerText = desc;
|
|
||||||
this.ps[1].style.color = color || 'black';
|
|
||||||
}
|
|
||||||
hideButton() {
|
|
||||||
document.querySelector('.button').style.display = 'none';
|
|
||||||
}
|
|
||||||
setChecked(checked) {
|
|
||||||
document.getElementById('enabled').checked = checked;
|
|
||||||
}
|
|
||||||
setToggleText(txt) {
|
|
||||||
document.getElementById('toggle').innerText = txt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
port.onMessage.addListener((m) => {
|
port.onMessage.addListener((m) => {
|
||||||
const { active, enabled, total, missingFeature } = m;
|
const { active, enabled, total, missingFeature } = m;
|
||||||
const popup = new Popup();
|
const popup = new Popup();
|
|
@ -1,18 +1,18 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Snowflake",
|
"name": "Snowflake",
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"description": "Snowflake is a WebRTC pluggable transport for Tor.",
|
"description": "Snowflake is a WebRTC pluggable transport for Tor.",
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["snowflake.js"],
|
"scripts": ["snowflake.js"],
|
||||||
"persistent": true
|
"persistent": true
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"32": "icons/status-on.png"
|
"32": "icons/status-on.png"
|
||||||
},
|
},
|
||||||
"default_title": "Snowflake",
|
"default_title": "Snowflake",
|
||||||
"default_popup": "popup.html"
|
"default_popup": "embed.html"
|
||||||
},
|
},
|
||||||
"permissions": ["storage"]
|
"permissions": ["storage"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="popup.css" />
|
|
||||||
<script src="popup.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="active">
|
|
||||||
<img src="icons/status-on.png" />
|
|
||||||
<p></p>
|
|
||||||
<p></p>
|
|
||||||
</div>
|
|
||||||
<div class="b button">
|
|
||||||
<label id="toggle" for="enabled">Turn On</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input id="enabled" type="checkbox" checked/>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="b learn">
|
|
||||||
<a target="_blank" href="https://snowflake.torproject.org/">Learn more</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|