Compare commits
159 Commits
master
...
commercial
Author | SHA1 | Date |
---|---|---|
AJ ONeal | 156c07a099 | |
AJ ONeal | 4d3be39043 | |
AJ ONeal | a4b39215ea | |
AJ ONeal | 6b619c8121 | |
AJ ONeal | dad775fcf6 | |
AJ ONeal | dbe3088908 | |
AJ ONeal | 285ee71486 | |
AJ ONeal | 04e399cf37 | |
AJ ONeal | 2bbfaeda2f | |
AJ ONeal | 7c5a451a9a | |
John Shaver | f0fda13b16 | |
John Shaver | 8046018dba | |
John Shaver | 7f2bd0a591 | |
John Shaver | 07468b41b2 | |
John Shaver | 40878066d1 | |
John Shaver | bba44656f7 | |
John Shaver | 408a049da2 | |
John Shaver | 137bf18c4e | |
John Shaver | 27ae2aeb48 | |
John Shaver | e77468106d | |
John Shaver | 947971dc3d | |
John Shaver | cde0525f63 | |
AJ ONeal | 2b79ca6b81 | |
AJ ONeal | f34f78cbfb | |
John Shaver | 682557dc86 | |
AJ ONeal | 6e1320fe9b | |
AJ ONeal | 883f8a55b1 | |
AJ ONeal | c2165578e1 | |
AJ ONeal | 00ad13efc7 | |
AJ ONeal | e6e8113cca | |
AJ ONeal | 374d6ee34b | |
AJ ONeal | 0fa68eef1e | |
AJ ONeal | 22e3730c4c | |
AJ ONeal | 2fc1ac7536 | |
AJ ONeal | eedf9fe896 | |
AJ ONeal | 34214e9afb | |
AJ ONeal | 7d11f15ebe | |
AJ ONeal | 739f86f1cc | |
AJ ONeal | e6c2282fcf | |
AJ ONeal | ec3f626560 | |
AJ ONeal | 168b2edfd6 | |
AJ ONeal | ec65fe31cc | |
AJ ONeal | bd15f45d1d | |
AJ ONeal | 2564b750e6 | |
John Shaver | d755b94bcd | |
John Shaver | 5e0b30f69e | |
John Shaver | 17d46bdcd5 | |
AJ ONeal | 2819117f10 | |
AJ ONeal | 222848cdd1 | |
AJ ONeal | d2df0d6ab3 | |
AJ ONeal | aad7c51834 | |
AJ ONeal | 616458a87a | |
AJ ONeal | b60658ee81 | |
AJ ONeal | f54c4dde7a | |
AJ ONeal | 8fe1f4d82a | |
AJ ONeal | 40ab6a4bb8 | |
AJ ONeal | acf8522195 | |
AJ ONeal | f828839b4d | |
AJ ONeal | 60ee3720e0 | |
AJ ONeal | 5b7f19e7a8 | |
AJ ONeal | 09660b0731 | |
AJ ONeal | 6972112782 | |
AJ ONeal | 004908e735 | |
AJ ONeal | 4e3e155460 | |
AJ ONeal | 5940d4bd28 | |
AJ ONeal | e4534d6076 | |
AJ ONeal | c3c4f8893f | |
AJ ONeal | 01c24d7eec | |
AJ ONeal | 4f2948d0f0 | |
AJ ONeal | a2b23b2509 | |
AJ ONeal | 64281e4c93 | |
John Shaver | 1099a75509 | |
John Shaver | 353329120f | |
AJ ONeal | c44608cbe7 | |
AJ ONeal | ae43b0859b | |
AJ ONeal | b386857d99 | |
AJ ONeal | d63b6cb370 | |
AJ ONeal | e611d945ed | |
AJ ONeal | de9aab8195 | |
AJ ONeal | 79e9728d19 | |
AJ ONeal | 5aed381963 | |
AJ ONeal | b5fd940429 | |
AJ ONeal | d12cddb1aa | |
AJ ONeal | f5126ad8ed | |
AJ ONeal | 36ab30c9f2 | |
AJ ONeal | 4e459ea617 | |
AJ ONeal | c045e4c712 | |
AJ ONeal | 16e30124e6 | |
AJ ONeal | d60b458f47 | |
AJ ONeal | 3773abdfdb | |
AJ ONeal | 6a1df2ee05 | |
AJ ONeal | 4bef0187f1 | |
AJ ONeal | db4e5c4f60 | |
AJ ONeal | 44cca52f50 | |
AJ ONeal | cda10951d8 | |
AJ ONeal | 5deefa9832 | |
AJ ONeal | 021629ea68 | |
AJ ONeal | 6b6ffd4647 | |
AJ ONeal | 57f1de5f2d | |
AJ ONeal | bbee698322 | |
AJ ONeal | cc58fcb98a | |
AJ ONeal | 5b90d5ef38 | |
AJ ONeal | 8aecbb1f56 | |
AJ ONeal | 40d54bcdad | |
AJ ONeal | a3b8cd6799 | |
AJ ONeal | dc67bee735 | |
AJ ONeal | 7fd28d55a1 | |
AJ ONeal | 589e4af90b | |
AJ ONeal | 6e60fc1750 | |
AJ ONeal | 6c2f039533 | |
AJ ONeal | cc2823a56a | |
AJ ONeal | cc5cbea486 | |
AJ ONeal | 9cd489f575 | |
AJ ONeal | 5d065c8bd0 | |
AJ ONeal | a9353a9a75 | |
AJ ONeal | 06b894a99d | |
AJ ONeal | d2fde5c38c | |
AJ ONeal | 4db9a8d53d | |
AJ ONeal | 809c14f91f | |
AJ ONeal | 79648af88c | |
AJ ONeal | 148cda8516 | |
AJ ONeal | 179256a88e | |
AJ ONeal | 95f9ca0844 | |
AJ ONeal | 33a565b19a | |
AJ ONeal | 114cc53dd4 | |
AJ ONeal | 09b1d5939e | |
AJ ONeal | 2603c322bd | |
AJ ONeal | 96631d1369 | |
AJ ONeal | 8a1e2f14e0 | |
AJ ONeal | 0013930dbd | |
AJ ONeal | 72986ccdc1 | |
AJ ONeal | ff52d1e4a8 | |
AJ ONeal | c1c7dfba02 | |
AJ ONeal | d6f517ed2b | |
AJ ONeal | f8ac7baa1a | |
AJ ONeal | f0df611886 | |
AJ ONeal | f8c7517b14 | |
AJ ONeal | f704a9fa19 | |
AJ ONeal | 2e36d903ae | |
AJ ONeal | 93c864d4d4 | |
AJ ONeal | 7de7cc651d | |
AJ ONeal | 97e5a847b8 | |
AJ ONeal | 20f59b6af7 | |
AJ ONeal | f2c983a129 | |
AJ ONeal | 06540352bf | |
AJ ONeal | 63065d6d65 | |
AJ ONeal | 5e1963d11f | |
AJ ONeal | 34b86bc7a3 | |
AJ ONeal | 08833123b2 | |
AJ ONeal | 4b44632f2e | |
AJ ONeal | d8e54c179d | |
AJ ONeal | a9b1c4204b | |
AJ ONeal | 46599c864c | |
AJ ONeal | ab35fdc40e | |
AJ ONeal | 114f298e8e | |
AJ ONeal | 2b9fadf4b4 | |
AJ ONeal | 2b2a0021aa | |
AJ ONeal | ee1f713bd3 | |
AJ ONeal | ad3b2e9167 |
|
@ -1,3 +1,8 @@
|
||||||
|
emails
|
||||||
|
lib/extensions/permissions.json
|
||||||
|
lib/extensions/permissions.json.bak
|
||||||
|
lib/extensions/admin/sclient/dist/
|
||||||
|
lib/extensions/admin/optify/dist/
|
||||||
node_modules.*
|
node_modules.*
|
||||||
include
|
include
|
||||||
bin/node
|
bin/node
|
||||||
|
@ -43,12 +48,3 @@ jspm_packages
|
||||||
|
|
||||||
# Optional REPL history
|
# Optional REPL history
|
||||||
.node_repl_history
|
.node_repl_history
|
||||||
|
|
||||||
# Snapcraft
|
|
||||||
/parts/
|
|
||||||
/prime/
|
|
||||||
/stage/
|
|
||||||
.snapcraft
|
|
||||||
*.snap
|
|
||||||
*.tar.bz2
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
var pkg = require('../package.json');
|
var pkg = require('../package.json');
|
||||||
|
|
||||||
var argv = process.argv.slice(2);
|
var argv = process.argv.slice(2);
|
||||||
|
@ -47,13 +51,7 @@ function applyConfig(config) {
|
||||||
} else {
|
} else {
|
||||||
state.Promise = require('bluebird');
|
state.Promise = require('bluebird');
|
||||||
}
|
}
|
||||||
state.tlsOptions = {
|
state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
|
||||||
// Handles disconnected devices
|
|
||||||
// TODO allow user to opt-in to wildcard hosting for a better error page?
|
|
||||||
SNICallback: function (servername, cb) {
|
|
||||||
return state.greenlock.tlsOptions.SNICallback(state.config.webminDomain || state.servernames[0], cb);
|
|
||||||
}
|
|
||||||
}; // TODO just close the sockets that would use this early? or use the admin servername
|
|
||||||
state.config = config;
|
state.config = config;
|
||||||
state.servernames = config.servernames || [];
|
state.servernames = config.servernames || [];
|
||||||
state.secret = state.config.secret;
|
state.secret = state.config.secret;
|
||||||
|
@ -73,54 +71,59 @@ function applyConfig(config) {
|
||||||
state.config.greenlock.configDir = require('os').homedir() + require('path').sep + 'acme';
|
state.config.greenlock.configDir = require('os').homedir() + require('path').sep + 'acme';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The domains being approved for the first time are listed in opts.domains
|
||||||
|
// Certs being renewed are listed in certs.altnames
|
||||||
function approveDomains(opts, certs, cb) {
|
function approveDomains(opts, certs, cb) {
|
||||||
if (state.debug) { console.log('[debug] approveDomains', opts.domains); }
|
if (state.debug) { console.log('[debug] approveDomains', opts.domains); }
|
||||||
// This is where you check your database and associated
|
|
||||||
// email addresses with domains and agreements and such
|
|
||||||
|
|
||||||
// The domains being approved for the first time are listed in opts.domains
|
function allow() {
|
||||||
// Certs being renewed are listed in certs.altnames
|
|
||||||
if (certs) {
|
|
||||||
opts.domains = certs.altnames;
|
|
||||||
cb(null, { options: opts, certs: certs });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.validHosts) { state.validHosts = {}; }
|
|
||||||
if (!state.validHosts[opts.domains[0]] && state.config.vhost) {
|
|
||||||
if (state.debug) { console.log('[sni] vhost checking is turned on'); }
|
|
||||||
var vhost = state.config.vhost.replace(/:hostname/, opts.domains[0]);
|
|
||||||
require('fs').readdir(vhost, function (err, nodes) {
|
|
||||||
if (state.debug) { console.log('[sni] checking fs vhost', opts.domains[0], !err); }
|
|
||||||
if (err) { check(); return; }
|
|
||||||
if (nodes) { approve(); }
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function approve() {
|
|
||||||
state.validHosts[opts.domains[0]] = true;
|
state.validHosts[opts.domains[0]] = true;
|
||||||
opts.email = state.config.email;
|
opts.email = state.config.email;
|
||||||
opts.agreeTos = state.config.agreeTos;
|
opts.agreeTos = state.config.agreeTos;
|
||||||
opts.communityMember = state.config.communityMember || state.config.greenlock.communityMember;
|
opts.communityMember = state.config.communityMember || state.config.greenlock.communityMember;
|
||||||
opts.challenges = {
|
opts.challenges = {
|
||||||
// TODO dns-01
|
// TODO dns-01
|
||||||
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' })
|
'http-01': require('le-challenge-fs').create({ webrootPath: path.join(os.tmpdir(), 'acme-challenges') })
|
||||||
};
|
};
|
||||||
opts.communityMember = state.config.communityMember;
|
opts.communityMember = state.config.communityMember;
|
||||||
cb(null, { options: opts, certs: certs });
|
cb(null, { options: opts, certs: certs });
|
||||||
}
|
}
|
||||||
|
|
||||||
function check() {
|
function deny() {
|
||||||
if (state.debug) { console.log('[sni] checking servername'); }
|
cb(new Error("[bin/telebit-relay.js] failed the approval chain '" + opts.domains[0] + "'"));
|
||||||
if (-1 !== state.servernames.indexOf(opts.domain) || -1 !== (state._servernames||[]).indexOf(opts.domain)) {
|
return;
|
||||||
approve();
|
|
||||||
} else {
|
|
||||||
cb(new Error("failed the approval chain '" + opts.domains[0] + "'"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check();
|
// 1) If the host was already allowed => allow
|
||||||
|
if (!state.validHosts) { state.validHosts = {}; }
|
||||||
|
if (state.validHosts[opts.domains[0]]) {
|
||||||
|
allow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) If the host is in the config => allow
|
||||||
|
if (state.debug) { console.log('[sni] checking servername'); }
|
||||||
|
if (-1 !== state.servernames.indexOf(opts.domain)
|
||||||
|
|| -1 !== (state._servernames||[]).indexOf(opts.domain)) {
|
||||||
|
allow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) If dynamic vhosting is allowed
|
||||||
|
// & a vhost folder exist for this domain => allow
|
||||||
|
if (state.config.vhost) {
|
||||||
|
if (state.debug) { console.log('[sni] vhost checking is turned on'); }
|
||||||
|
var vhost = state.config.vhost.replace(/:hostname/, opts.domains[0]);
|
||||||
|
require('fs').readdir(vhost, function (err, nodes) {
|
||||||
|
if (state.debug) { console.log('[sni] checking fs vhost', opts.domains[0], !err); }
|
||||||
|
if (err) { deny(); return; }
|
||||||
|
if (nodes) { allow(); }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) fallback => fail
|
||||||
|
deny();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.greenlock = Greenlock.create({
|
state.greenlock = Greenlock.create({
|
||||||
|
@ -202,7 +205,7 @@ function applyConfig(config) {
|
||||||
//});
|
//});
|
||||||
}
|
}
|
||||||
|
|
||||||
require('fs').readFile(confpath, 'utf8', function (err, text) {
|
fs.readFile(confpath, 'utf8', function (err, text) {
|
||||||
var config;
|
var config;
|
||||||
|
|
||||||
var recase = require('recase').create({});
|
var recase = require('recase').create({});
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
email: jon@example.com # must be valid (for certificate recovery and security alerts)
|
||||||
|
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
||||||
|
community_member: true # receive infrequent relevant updates
|
||||||
|
telemetry: true # contribute to project telemetric data
|
||||||
|
webmin_domain: telebit.example.com
|
||||||
|
api_domain: api.telebit.example.com
|
||||||
|
shared_domain: telebit.example.com
|
||||||
|
shared_domains:
|
||||||
|
- telebit.example.com
|
||||||
|
servernames: # hostnames that direct to the Telebit Relay admin console
|
||||||
|
- telebit.example.com
|
||||||
|
- www.telebit.example.com
|
||||||
|
- api.telebit.example.com
|
||||||
|
vhost: /srv/www/:hostname # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public)
|
||||||
|
trusted_issuers:
|
||||||
|
- oauth3.org
|
||||||
|
mailer:
|
||||||
|
url: 'https://api.mailgun.net/v3/example.com/messages'
|
||||||
|
api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
from: 'Telebit Wizard <wizard@example.com>'
|
||||||
|
greenlock:
|
||||||
|
version: 'draft-11'
|
||||||
|
server: 'https://acme-v02.api.letsencrypt.org/directory'
|
||||||
|
store:
|
||||||
|
strategy: le-store-certbot # certificate storage plugin
|
||||||
|
config_dir: /opt/telebit-relay/etc/acme # directory for ssl certificates
|
||||||
|
secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))"
|
|
@ -1,17 +1,24 @@
|
||||||
email: 'jon@example.com' # must be valid (for certificate recovery and security alerts)
|
email: coolaj86@gmail.com
|
||||||
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
agree_tos: true
|
||||||
community_member: true # receive infrequent relevant updates
|
community_member: true
|
||||||
telemetry: true # contribute to project telemetric data
|
telemetry: true
|
||||||
webmin_domain: example.com
|
webmin_domain: example.com
|
||||||
shared_domain: xm.pl
|
api_domain: example.com
|
||||||
servernames: # hostnames that direct to the Telebit Relay admin console
|
shared_domain: example.com
|
||||||
- telebit.example.com
|
servernames:
|
||||||
- telebit.example.net
|
- www.example.com
|
||||||
vhost: /srv/www/:hostname # load secure websites at this path (uses template string, i.e. /var/www/:hostname/public)
|
- example.com
|
||||||
|
- api.example.com
|
||||||
|
vhost: /srv/www/:hostname
|
||||||
greenlock:
|
greenlock:
|
||||||
version: 'draft-11'
|
version: 'draft-11'
|
||||||
server: 'https://acme-v02.api.letsencrypt.org/directory'
|
server: 'https://acme-v02.api.letsencrypt.org/directory'
|
||||||
store:
|
store:
|
||||||
strategy: le-store-certbot # certificate storage plugin
|
strategy: le-store-certbot
|
||||||
config_dir: /etc/acme # directory for ssl certificates
|
config_dir: /opt/telebit-relay/etc/acme
|
||||||
secret: '' # generate with node -e "console.log(crypto.randomBytes(16).toString('hex'))"
|
secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
mailer:
|
||||||
|
url: 'https://api.mailgun.net/v3/EXAMPLE.COM/messages'
|
||||||
|
api_key: 'key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
from: 'Example Mailer <MALIER@EXAMPLE.COM>'
|
||||||
|
debug: true
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
servernames: [ 'telebit.cloud' ]
|
||||||
|
email: 'coolaj86@gmail.com'
|
||||||
|
agree_tos: true
|
||||||
|
community_member: false
|
||||||
|
vhost: /srv/www/:hostname
|
|
@ -179,6 +179,9 @@ if [ ! -f "$TELEBIT_RELAY_PATH/etc/$my_app.yml" ]; then
|
||||||
sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'email: $my_email' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'secret: $my_secret' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "echo 'servernames: [ $my_servername ]' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'webmin_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'api_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
|
sudo bash -c "echo 'shared_domain: $my_servername' >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
sudo bash -c "cat $TELEBIT_RELAY_PATH/examples/$my_app.yml.tpl >> $TELEBIT_RELAY_PATH/etc/$my_app.yml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var timeago = require('./ago.js').AGO;
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
[ 1.5 * 1000 // a moment ago
|
|
||||||
, 4.5 * 1000 // moments ago
|
|
||||||
, 10 * 1000 // 10 seconds ago
|
|
||||||
, 59 * 1000 // a minute ago
|
|
||||||
, 60 * 1000 // a minute ago
|
|
||||||
, 61 * 1000 // a minute ago
|
|
||||||
, 119 * 1000 // a minute ago
|
|
||||||
, 120 * 1000 // 2 minutes ago
|
|
||||||
, 121 * 1000 // 2 minutes ago
|
|
||||||
, (60 * 60 * 1000) - 1000 // 59 minutes ago
|
|
||||||
, 1 * 60 * 60 * 1000 // an hour ago
|
|
||||||
, 1.5 * 60 * 60 * 1000 // an hour ago
|
|
||||||
, 2.5 * 60 * 60 * 1000 // 2 hours ago
|
|
||||||
, 1.5 * 24 * 60 * 60 * 1000 // a day ago
|
|
||||||
, 2.5 * 24 * 60 * 60 * 1000 // 2 days ago
|
|
||||||
, 7 * 24 * 60 * 60 * 1000 // a week ago
|
|
||||||
, 14 * 24 * 60 * 60 * 1000 // 2 weeks ago
|
|
||||||
, 27 * 24 * 60 * 60 * 1000 // 3 weeks ago
|
|
||||||
, 28 * 24 * 60 * 60 * 1000 // 4 weeks ago
|
|
||||||
, 29 * 24 * 60 * 60 * 1000 // 4 weeks ago
|
|
||||||
, 1.5 * 30 * 24 * 60 * 60 * 1000 // a month ago
|
|
||||||
, 2.5 * 30 * 24 * 60 * 60 * 1000 // 2 months ago
|
|
||||||
, (12 * 30 * 24 * 60 * 60 * 1000) + 1000 // 12 months ago
|
|
||||||
, 13 * 30 * 24 * 60 * 60 * 1000 // over a year ago
|
|
||||||
].forEach(function (d) {
|
|
||||||
console.log(d, '=', timeago(d));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test();
|
|
50
lib/ago.js
|
@ -1,50 +0,0 @@
|
||||||
;(function (exports) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
exports.AGO = function timeago(ms) {
|
|
||||||
var ago = Math.floor(ms / 1000);
|
|
||||||
var part = 0;
|
|
||||||
|
|
||||||
if (ago < 2) { return "a moment ago"; }
|
|
||||||
if (ago < 5) { return "moments ago"; }
|
|
||||||
if (ago < 60) { return ago + " seconds ago"; }
|
|
||||||
|
|
||||||
if (ago < 120) { return "a minute ago"; }
|
|
||||||
if (ago < 3600) {
|
|
||||||
while (ago >= 60) { ago -= 60; part += 1; }
|
|
||||||
return part + " minutes ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ago < 7200) { return "an hour ago"; }
|
|
||||||
if (ago < 86400) {
|
|
||||||
while (ago >= 3600) { ago -= 3600; part += 1; }
|
|
||||||
return part + " hours ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ago < 172800) { return "a day ago"; }
|
|
||||||
if (ago < 604800) {
|
|
||||||
while (ago >= 172800) { ago -= 172800; part += 1; }
|
|
||||||
return part + " days ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ago < 1209600) { return "a week ago"; }
|
|
||||||
if (ago < 2592000) {
|
|
||||||
while (ago >= 604800) { ago -= 604800; part += 1; }
|
|
||||||
return part + " weeks ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ago < 5184000) { return "a month ago"; }
|
|
||||||
if (ago < 31536001) {
|
|
||||||
while (ago >= 2592000) { ago -= 2592000; part += 1; }
|
|
||||||
return part + " months ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ago < 315360000) { // 10 years
|
|
||||||
return "more than year ago";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO never
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
}('undefined' !== typeof module ? module.exports : window));
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Devices = module.exports;
|
var Devices = module.exports;
|
||||||
// TODO enumerate store's keys and device's keys for documentation
|
|
||||||
Devices.addPort = function (store, serverport, newDevice) {
|
Devices.addPort = function (store, serverport, newDevice) {
|
||||||
// TODO make special
|
// TODO make special
|
||||||
return Devices.add(store, serverport, newDevice, true);
|
return Devices.add(store, serverport, newDevice, true);
|
||||||
|
@ -15,7 +14,6 @@ Devices.add = function (store, servername, newDevice, isPort) {
|
||||||
if (!store._domains) { store._domains = {}; }
|
if (!store._domains) { store._domains = {}; }
|
||||||
if (!store._domains[servername]) { store._domains[servername] = []; }
|
if (!store._domains[servername]) { store._domains[servername] = []; }
|
||||||
store._domains[servername].push(newDevice);
|
store._domains[servername].push(newDevice);
|
||||||
Devices.touch(store, servername);
|
|
||||||
|
|
||||||
// add device
|
// add device
|
||||||
// TODO only use a device id
|
// TODO only use a device id
|
||||||
|
@ -97,6 +95,7 @@ Devices.list = function (store, servername) {
|
||||||
// aliases have ._primary which is the name of the original
|
// aliases have ._primary which is the name of the original
|
||||||
return store._domains[servername]._primary && store._domains[store._domains[servername]._primary] || store._domains[servername];
|
return store._domains[servername]._primary && store._domains[store._domains[servername]._primary] || store._domains[servername];
|
||||||
}
|
}
|
||||||
|
|
||||||
// There wasn't an exact match so check any of the wildcard domains, sorted longest
|
// There wasn't an exact match so check any of the wildcard domains, sorted longest
|
||||||
// first so the one with the biggest natural match with be found first.
|
// first so the one with the biggest natural match with be found first.
|
||||||
var deviceList = [];
|
var deviceList = [];
|
||||||
|
@ -105,7 +104,10 @@ Devices.list = function (store, servername) {
|
||||||
}).sort(function (a, b) {
|
}).sort(function (a, b) {
|
||||||
return b.length - a.length;
|
return b.length - a.length;
|
||||||
}).some(function (pattern) {
|
}).some(function (pattern) {
|
||||||
|
// '.example.com' = '*.example.com'.split(1)
|
||||||
var subPiece = pattern.slice(1);
|
var subPiece = pattern.slice(1);
|
||||||
|
// '.com' = 'sub.example.com'.slice(-4)
|
||||||
|
// '.example.com' = 'sub.example.com'.slice(-12)
|
||||||
if (subPiece === servername.slice(-subPiece.length)) {
|
if (subPiece === servername.slice(-subPiece.length)) {
|
||||||
console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"');
|
console.log('[Devices.list] "'+servername+'" matches "'+pattern+'"');
|
||||||
deviceList = store._domains[pattern];
|
deviceList = store._domains[pattern];
|
||||||
|
@ -128,11 +130,7 @@ Devices.active = function (store, id) {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
Devices.exist = function (store, servername) {
|
Devices.exist = function (store, servername) {
|
||||||
if (Devices.list(store, servername).length) {
|
return !!(Devices.list(store, servername).length);
|
||||||
Devices.touch(store, servername);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
Devices.next = function (store, servername) {
|
Devices.next = function (store, servername) {
|
||||||
var devices = Devices.list(store, servername);
|
var devices = Devices.list(store, servername);
|
||||||
|
@ -144,20 +142,5 @@ Devices.next = function (store, servername) {
|
||||||
device = devices[devices._index || 0];
|
device = devices[devices._index || 0];
|
||||||
devices._index = (devices._index || 0) + 1;
|
devices._index = (devices._index || 0) + 1;
|
||||||
|
|
||||||
if (device) { Devices.touch(store, servername); }
|
|
||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
Devices.touchDevice = function (store, device) {
|
|
||||||
// TODO use device.id (which will be pubkey thumbprint) and store._devices[id].domainsMap
|
|
||||||
Object.keys(device.domainsMap).forEach(function (servername) {
|
|
||||||
Devices.touch(store, servername);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Devices.touch = function (store, servername) {
|
|
||||||
if (!store._recency) { store._recency = {}; }
|
|
||||||
store._recency[servername] = Date.now();
|
|
||||||
};
|
|
||||||
Devices.lastSeen = function (store, servername) {
|
|
||||||
if (!store._recency) { store._recency = {}; }
|
|
||||||
return store._recency[servername] || 0;
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
commercial
|
||||||
|
|
||||||
|
Copyright ppl 2018
|
|
@ -0,0 +1 @@
|
||||||
|
_apis
|
|
@ -0,0 +1 @@
|
||||||
|
oauth3.org
|
|
@ -0,0 +1 @@
|
||||||
|
../assets/oauth3.org/_apis/oauth3.org
|
|
@ -0,0 +1,11 @@
|
||||||
|
{ "terms_of_service": ":hostname/tos/"
|
||||||
|
, "api_host": "api.:hostname"
|
||||||
|
, "pair_request": {
|
||||||
|
"method": "POST"
|
||||||
|
, "pathname": "api/telebit.cloud/pair_request"
|
||||||
|
}
|
||||||
|
, "tunnel": {
|
||||||
|
"method": "wss"
|
||||||
|
, "pathname": ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Telebit Account</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="v-app">
|
||||||
|
<div v-if="spinner" style="position: absolute; width: 100%; height: 100%; background-color: #ddd;">Loading... </div>
|
||||||
|
|
||||||
|
<div v-if="!hasAccount">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form class="js-auth-form" v-on:submit.prevent="login()">
|
||||||
|
<input class="js-auth-subject" v-model="newEmail" placeholder="email" type="email" required/>
|
||||||
|
<button class="js-auth-submit" type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasAccount">
|
||||||
|
<h1>Account</h1>
|
||||||
|
<button v-on:click.prevent.stop="logout()" type="click">Logout</button>
|
||||||
|
<!-- not yet -->
|
||||||
|
<!--form v-on:submit.prevent="challengeEmail()">
|
||||||
|
Authorize another email:
|
||||||
|
<input v-model="newEmail" placeholder="jon@example.com" type="email" required/>
|
||||||
|
<button type="submit">Next</button>
|
||||||
|
</form-->
|
||||||
|
|
||||||
|
<div v-if="claims.length">
|
||||||
|
<h3>Pending Claims</h3>
|
||||||
|
<p>If your DNS host supports ANAME records, please use those instead of CNAMEs.</p>
|
||||||
|
<p>If CNAMEs are not supported, set an A record to {{ site.deviceDomainA }}.</p>
|
||||||
|
<ol>
|
||||||
|
<li v-for="claim in claims">
|
||||||
|
<span>{{ claim.value }}</span>
|
||||||
|
<br>
|
||||||
|
<span v-if="'dns' === claim.type">CNAME <span v-if="claim.wildcard">*.</span>{{ claim.value }}: {{ site.deviceDomain }}</span>
|
||||||
|
<br>
|
||||||
|
<span v-if="'dns' === claim.type">TXT _claim-challenge.{{ claim.value }}: {{ claim.challenge }}</span>
|
||||||
|
<br>
|
||||||
|
<button v-on:click.prevent="checkDns(claim)">Check</button>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Devices</h3>
|
||||||
|
<div v-if="!devices.length">
|
||||||
|
You can add up to 5 devices:
|
||||||
|
<pre><code>curl -sf https://get.telebit.io/ | bash</code></pre>
|
||||||
|
</div>
|
||||||
|
<div v-if="devices.length">
|
||||||
|
<ol>
|
||||||
|
<li v-for="device in devices">
|
||||||
|
<span v-if="device.id">{{ device.id }}</span> {{ device.socketId }}
|
||||||
|
<ul>
|
||||||
|
<li><form v-on:submit.stop.prevent="pushDomain(device)">
|
||||||
|
<input type="text" v-model="device.newDomain" placeholder="ex: jon.telebit.com"></input><button type="submit">Push</button>
|
||||||
|
</form></li>
|
||||||
|
<li v-for="name in device.names">{{ name }}</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Domains</h3>
|
||||||
|
<form v-on:submit.prevent="challengeDns()">
|
||||||
|
Add a custom domain:
|
||||||
|
<input v-model="newDomain" placeholder="example.com" type="text" required/>
|
||||||
|
<button type="submit">Next</button>
|
||||||
|
</form>
|
||||||
|
<div v-if="domains.length">
|
||||||
|
<ol>
|
||||||
|
<li v-for="domain in domains">
|
||||||
|
<span v-if="domain.wildcard">*.</span>{{ domain.name }} <span v-if="domain.hostname">- {{domain.hostname}} ({{domain.os}} {{domain.arch}})</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Debug: Token</h3>
|
||||||
|
<pre><code v-text="token"></code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- development version, includes helpful console warnings -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||||
|
|
||||||
|
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
||||||
|
<script src="js/account.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8e2e09f5823ae919c615c9c3b21114e01096b1ee
|
|
@ -0,0 +1,4 @@
|
||||||
|
channel version date
|
||||||
|
prod v0.15.4 2018-06-14T09:37:22Z
|
||||||
|
beta v0.15.3 2018-06-14T07:07:21Z
|
||||||
|
prod v0.12.0 2018-06-07T07:43:21Z
|
|
@ -0,0 +1,86 @@
|
||||||
|
Release Notes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
|
||||||
|
* v0.20.6 - protocol upgrade
|
||||||
|
|
||||||
|
Re: v0.20.6
|
||||||
|
===========
|
||||||
|
|
||||||
|
Saturday, Sept 29, 2018
|
||||||
|
|
||||||
|
This version is a required update. I had to make some changes to the network
|
||||||
|
protocol that were easy enough to make backwards-compatible in the client, but
|
||||||
|
not worth the effort to do so on the server.
|
||||||
|
|
||||||
|
Mac, Linux, Raspberry Pi Users:
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
curl -fsSL https://get.telebit.io | bash
|
||||||
|
|
||||||
|
That should be quick and easy, but you may need to reboot your computer.
|
||||||
|
|
||||||
|
Windows & npm users
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
npm install -g npm
|
||||||
|
|
||||||
|
Note that on Windows the upgrade will **NOT** work while Telebit is
|
||||||
|
running. `telebit restart` should kill it but, on Windows, won't actually
|
||||||
|
restart it.
|
||||||
|
|
||||||
|
This is not well tested, so please contact me (aj@ppl.family) if you have any
|
||||||
|
trouble.
|
||||||
|
|
||||||
|
Upgrading *really* old versions
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
If you have a version of telebit prior to v0.18.1 (which may not even list its
|
||||||
|
version in `telebit help` yet), it'll probably be easiest to manually remove
|
||||||
|
the old telebit files first:
|
||||||
|
|
||||||
|
sudo rm -rf ~/Applications/telebit* ~/.config/telebit*
|
||||||
|
sudo rm -rf /opt/telebit* /etc/telebit* /etc/systemd/system/telebit*
|
||||||
|
|
||||||
|
You'll lose your current domain. If that's an issue, contact me and we can work
|
||||||
|
it out.
|
||||||
|
|
||||||
|
Rationale
|
||||||
|
---------
|
||||||
|
|
||||||
|
> "If it ain't broke, don't fix it" - Ancient Redneck Proverb
|
||||||
|
|
||||||
|
> "When is broke, is most right time to fix" - Ageless Chinese Adage
|
||||||
|
|
||||||
|
There's a delicate balance between the two and in my infinite wisdom I've
|
||||||
|
decided that now is the right time to fix.
|
||||||
|
There are some rather disruptive bugs in the network protocol and fixing them
|
||||||
|
means breaking most existing clients.
|
||||||
|
|
||||||
|
If you've been using telebit on a daily basis, especially with ssh, I believe
|
||||||
|
that'll you see benefit immediately and even moreso once the server is updated.
|
||||||
|
It's worth it.
|
||||||
|
|
||||||
|
Additional Notes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
A number of good fixes are in here:
|
||||||
|
|
||||||
|
|
||||||
|
### `telebit help`
|
||||||
|
|
||||||
|
The in-app cli help is now correctly documented. Not everything _works_ as
|
||||||
|
documented, however. Feel free to poke around and give me feedback.
|
||||||
|
|
||||||
|
|
||||||
|
### `telebit ssh none`
|
||||||
|
|
||||||
|
Previously `telebit ssh none` behaved identically to `telebit ssh auto`.
|
||||||
|
|
||||||
|
The output correctly showed the actual behavior, but it didn't make sense.
|
||||||
|
|
||||||
|
Bascially this was happening: `telebit.ssh = telebit.ssh || 22`. So when it
|
||||||
|
it was `false` it became `true`
|
||||||
|
|
||||||
|
It was changed to this `if (!('ssh' in telebit)) { telebit.ssh = 22; }`.
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict';
|
||||||
|
module.exports = function (opts, cb) {
|
||||||
|
var pkg = opts.package;
|
||||||
|
var root = opts.root;
|
||||||
|
|
||||||
|
//console.log('DEBUG pkg', pkg);
|
||||||
|
//console.log('DEBUG root', root);
|
||||||
|
process.nextTick(function () {
|
||||||
|
cb(null, { message: "upgrade complete" });
|
||||||
|
});
|
||||||
|
return { message: "placeholder upgrade: nothing to do yet" };
|
||||||
|
};
|
|
@ -0,0 +1,357 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=900">
|
||||||
|
<title>Telebit™ Cloud</title>
|
||||||
|
<link href="static-site-assets/styles/main.css" rel="stylesheet">
|
||||||
|
<link href="static-site-assets/styles/vertical-slide.css" rel="stylesheet">
|
||||||
|
<link href="static-site-assets/styles/1200.css" rel="stylesheet" media="(max-width:1075px)">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: block;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: block;
|
||||||
|
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Code Pro';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(/static-site-assets/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">Telebit</div>
|
||||||
|
<ul class="navigation-menu">
|
||||||
|
<li>
|
||||||
|
<a class="nav-link" target="_blank" href="https://git.coolaj86.com/coolaj86/telebit.js">Docs</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="nav-link" target="_blank" href="https://www.patreon.com/coolaj86">Donate</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="link-button nav-link" href="#download-section">Download</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</header><div class="hero">
|
||||||
|
<div class="container">
|
||||||
|
<div class="spiel">
|
||||||
|
<h1>Access your devices
|
||||||
|
<br>Share your stuff
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div aria-hidden="true" class="demo-row">
|
||||||
|
<div class="demo-container">
|
||||||
|
<div class="demo-browser">
|
||||||
|
<div class="demo-browser-header">
|
||||||
|
<div class="demo-browser-buttons">
|
||||||
|
<div></div><div></div><div></div>
|
||||||
|
</div>
|
||||||
|
<div class="demo-browser-address-bar">
|
||||||
|
<img src="static-site-assets/images/green-secure.png">
|
||||||
|
<div class="demo-browser-url">
|
||||||
|
https://jondoe.telebit.io
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="demo-browser-body">
|
||||||
|
Hello world!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-terminal">
|
||||||
|
<div class="demo-terminal-input">
|
||||||
|
telebit http 3000
|
||||||
|
</div>
|
||||||
|
<div class="demo-terminal-line">
|
||||||
|
</div>
|
||||||
|
<div class="demo-terminal-output">
|
||||||
|
Forwarding https://jondoe.telebit.io => localhost:3000
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="container quickstart-container">
|
||||||
|
<h2 class="use-it">Use it <div class="sliding-vertical">
|
||||||
|
<!-- to add more of or remove some of these, you will also need to update
|
||||||
|
./static-site-assets/styles/vertical-slide.css
|
||||||
|
to allow for the correct number of values. Formulas for calculating
|
||||||
|
new values are included in the style comments.
|
||||||
|
-->
|
||||||
|
<span class="accent-color">to test your webhooks.</span>
|
||||||
|
<span class="accent-color">to show your project to Mom.</span>
|
||||||
|
<span class="accent-color">to test your site on mobile.</span>
|
||||||
|
<span class="accent-color">to work from 127.0.0.1.</span>
|
||||||
|
<span class="accent-color">to access your raspberry pi.</span>
|
||||||
|
<span class="accent-color">to build peer-to-peer apps.</span>
|
||||||
|
</div></h2>
|
||||||
|
<h2 id="download-section">Quickstart with bash</h2>
|
||||||
|
<div class="quickstart-step">
|
||||||
|
<div class="quickstart-step-text">
|
||||||
|
<div class="quickstart-step-number">1</div>
|
||||||
|
<div class="quickstart-step-name">Install Telebit</div>
|
||||||
|
</div>
|
||||||
|
<pre class="quickstart-terminal qickstart-terminal-prompt">curl https://get.telebit.io/ | bash</pre>
|
||||||
|
</div>
|
||||||
|
<div class="quickstart-step">
|
||||||
|
<div class="quickstart-step-text">
|
||||||
|
<div class="quickstart-step-number">2</div>
|
||||||
|
<div class="quickstart-step-name">Claim your device via Email</div>
|
||||||
|
</div>
|
||||||
|
<pre class="quickstart-terminal">Hello!
|
||||||
|
|
||||||
|
Want to use 'Jon's Macbook Pro' with Telebit?
|
||||||
|
Just confirm your email address:
|
||||||
|
|
||||||
|
<u>Confirm Email Address</u></pre>
|
||||||
|
</div>
|
||||||
|
<div class="quickstart-step">
|
||||||
|
<div class="quickstart-step-text">
|
||||||
|
<div class="quickstart-step-number">3</div>
|
||||||
|
<div class="quickstart-step-name">Enjoy Anytime, Anywhere Access</div>
|
||||||
|
</div>
|
||||||
|
<pre class="quickstart-terminal"><strong>For Local Development</strong>
|
||||||
|
|
||||||
|
<code class="quickstart-input">~/telebit http 3000</code>
|
||||||
|
<code class="quickstart-output">Forwarding https://jondoe.telebit.io => localhost:3000</code>
|
||||||
|
|
||||||
|
<code class="quickstart-input">curl -fsSL https://jondoe.telebit.io/</code>
|
||||||
|
|
||||||
|
<strong>For Sharing Files</strong>
|
||||||
|
|
||||||
|
<code class="quickstart-input">~/telebit http ./project.zip</code>
|
||||||
|
<code class="quickstart-output">Serving ./project.zip as https://jondoe.telebit.io</code>
|
||||||
|
|
||||||
|
<code class="quickstart-input">curl -fsSL https://jondoe.telebit.io/</code>
|
||||||
|
|
||||||
|
<strong>For Access with SSH</strong>
|
||||||
|
|
||||||
|
<code class="quickstart-input">~/telebit ssh auto</code>
|
||||||
|
<code class="quickstart-output">Forwarding jondoe.telebit.io -p 5050 => localhost:22</code>
|
||||||
|
<code class="quickstart-output">Forwarding ssh+https (openssl proxy) => localhost:22</code>
|
||||||
|
|
||||||
|
<code class="quickstart-input">ssh -p 5050 jondoe.telebit.io</code>
|
||||||
|
<code class="quickstart-input">ssh -o ProxyCommand="<a href="sclient/">sclient</a> %h" jondoe.telebit.io</code>
|
||||||
|
|
||||||
|
<strong>For Debugging with TCP</strong>
|
||||||
|
|
||||||
|
<code class="quickstart-input">~/telebit tcp 9000</code>
|
||||||
|
<code class="quickstart-output">Forwarding jondoe.telebit.io -p 5050 => localhost:9000</code>
|
||||||
|
|
||||||
|
<code class="quickstart-input">netcat jondoe.telebit.io 5050</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="install-for">
|
||||||
|
<div class="container">
|
||||||
|
<h3>Install For</h3>
|
||||||
|
<div class="install-badges">
|
||||||
|
<a class="install-badge" target="_blank"
|
||||||
|
href="https://git.coolaj86.com/coolaj86/telebit.js#windows--nodejs">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#000" fill-rule="nonzero" d="M2
|
||||||
|
4.819l8.11-1.105.004 7.823-8.107.047L2 4.819zm8.107
|
||||||
|
7.62l.006 7.83-8.107-1.114v-6.769l8.1.053zm.983-8.87L21.844
|
||||||
|
2v9.438l-10.754.085V3.57zm10.757 8.944l-.003 9.395L11.09
|
||||||
|
20.39l-.015-7.895 10.772.018z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span>Windows</span>
|
||||||
|
</a>
|
||||||
|
<a class="install-badge" target="_blank"
|
||||||
|
href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#1A1A1A" d="M21.41 8.22c-1.667 1.179-2.5
|
||||||
|
2.597-2.5 4.254 0 1.986 1.03 3.509 3.09 4.57-.553 1.6-1.354
|
||||||
|
2.993-2.402 4.178C18.549 22.407 17.592 23 16.726 23c-.408
|
||||||
|
0-.965-.135-1.67-.404l-.34-.13c-.69-.27-1.302-.404-1.834-.404-.502
|
||||||
|
0-1.052.105-1.649.316l-.426.153-.535.218c-.422.167-.848.251-1.277.251-1.012
|
||||||
|
0-2.13-.833-3.352-2.498C3.88 18.117 3 15.518 3 12.704c0-2
|
||||||
|
.55-3.61 1.649-4.832 1.1-1.222 2.555-1.833 4.368-1.833.677
|
||||||
|
0 1.31.124
|
||||||
|
1.9.371l.404.164.426.174c.378.16.684.24.917.24.298 0
|
||||||
|
.63-.069.993-.207l.557-.218.415-.153c.663-.24 1.394-.36
|
||||||
|
2.195-.36 1.9 0 3.429.724 4.586 2.17zM16.911
|
||||||
|
1c.022.255.033.45.033.589 0 1.258-.458 2.361-1.376
|
||||||
|
3.31-.917.95-1.983 1.424-3.199 1.424a5.474 5.474 0 0
|
||||||
|
1-.055-.611c0-1.069.426-2.072 1.278-3.01.852-.938
|
||||||
|
1.838-1.487 2.96-1.647.08-.015.2-.033.36-.055z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span>Mac</span>
|
||||||
|
</a>
|
||||||
|
<a class="install-badge" target="_blank"
|
||||||
|
href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<g transform="translate(1 2)">
|
||||||
|
<rect width="22" height="20" fill="#000" rx="1"/>
|
||||||
|
<path fill="#FFF" d="M6.495 3.942v1.125l-4.12
|
||||||
|
1.566V5.551l2.882-1.047-2.882-1.056V2.375l4.12
|
||||||
|
1.567zm.32 3.592h4.327v.779H6.814v-.78z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span>Linux</span>
|
||||||
|
</a>
|
||||||
|
<a class="install-badge" target="_blank"
|
||||||
|
href="https://git.coolaj86.com/coolaj86/telebit.js#mac--linux">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<g fill="#000" fill-rule="nonzero">
|
||||||
|
<path d="M6.502 3.124c2.152 1.11 3.403 2.007 4.088
|
||||||
|
2.771-.35 1.407-2.182 1.471-2.851
|
||||||
|
1.432.137-.064.251-.14.292-.258-.168-.12-.764-.012-1.18-.246.16-.033.235-.065.31-.183-.394-.125-.817-.233-1.066-.441.134.002.26.03.435-.092-.352-.19-.727-.34-1.019-.63.182-.004.378-.001.435-.068a3.924
|
||||||
|
3.924 0 0
|
||||||
|
1-.819-.665c.255.031.363.005.424-.04-.243-.25-.552-.46-.698-.767.189.065.362.09.486-.006-.082-.186-.437-.296-.641-.733.199.02.41.044.452
|
||||||
|
0-.092-.376-.25-.588-.406-.807.426-.006 1.071.002
|
||||||
|
1.042-.034l-.263-.27c.416-.112.841.018
|
||||||
|
1.15.115.14-.11-.002-.248-.171-.39.353.048.673.129.962.241.154-.14-.1-.279-.224-.418.547.104.778.25
|
||||||
|
1.008.395.167-.16.01-.296-.103-.435.412.153.624.35.848.544.075-.102.192-.177.051-.424.293.169.513.367.676.59.18-.115.108-.273.109-.418.304.247.497.51.733.767.047-.034.089-.152.126-.338.725.704
|
||||||
|
1.75 2.476.263
|
||||||
|
3.179-1.264-1.044-2.775-1.802-4.45-2.371zM17.921
|
||||||
|
3.124c-2.152 1.11-3.403 2.007-4.089 2.771.351 1.407
|
||||||
|
2.183 1.471 2.852
|
||||||
|
1.432-.137-.064-.251-.14-.292-.258.168-.12.764-.012
|
||||||
|
1.18-.246-.16-.033-.235-.065-.31-.183.393-.125.817-.233
|
||||||
|
1.066-.441-.135.002-.26.03-.436-.092.352-.19.728-.34
|
||||||
|
1.02-.63-.182-.004-.379-.001-.436-.068.323-.2.594-.422.82-.665-.255.031-.363.005-.424-.04.243-.25.551-.46.698-.767-.189.065-.362.09-.487-.006.083-.186.438-.296.642-.733-.2.02-.41.044-.453
|
||||||
|
0
|
||||||
|
.093-.376.251-.588.407-.807-.426-.006-1.071.002-1.042-.034l.263-.27c-.416-.112-.842.018-1.15.115-.14-.11.002-.248.171-.39a4.182
|
||||||
|
4.182 0 0
|
||||||
|
0-.962.241c-.154-.14.1-.279.223-.418-.546.104-.778.25-1.008.395-.166-.16-.01-.296.103-.435-.411.153-.624.35-.847.544-.076-.102-.192-.177-.052-.424a2.149
|
||||||
|
2.149 0 0
|
||||||
|
0-.675.59c-.181-.115-.108-.273-.109-.418-.304.247-.497.51-.733.767-.048-.034-.09-.152-.126-.338-.725.704-1.75
|
||||||
|
2.476-.263 3.179 1.264-1.044 2.775-1.802
|
||||||
|
4.449-2.371zM14.818 17.45c0 1.313-1.154 2.377-2.578
|
||||||
|
2.377s-2.578-1.064-2.578-2.377c0-1.313 1.154-2.377
|
||||||
|
2.578-2.377s2.578 1.064 2.578 2.377zM10.153
|
||||||
|
10.363c1.204.426 1.773 1.922 1.27 3.343-.501 1.42-1.884
|
||||||
|
2.227-3.088
|
||||||
|
1.801-1.204-.426-1.773-1.922-1.271-3.343.502-1.42
|
||||||
|
1.885-2.227 3.09-1.801zM14.226 10.236c-1.204.426-1.773
|
||||||
|
1.922-1.27 3.343.501 1.42 1.884 2.227 3.088 1.801
|
||||||
|
1.204-.425 1.773-1.922
|
||||||
|
1.271-3.342-.502-1.42-1.885-2.227-3.089-1.802zM5.41
|
||||||
|
11.803c1.153-.309.389 4.771-.55
|
||||||
|
4.355-1.032-.83-1.364-3.262.55-4.355zM18.737
|
||||||
|
11.74c-1.154-.309-.39 4.771.549 4.354 1.032-.83
|
||||||
|
1.364-3.261-.55-4.354zM14.818 7.957c1.99-.336 3.647.847
|
||||||
|
3.58 3.005-.066.827-4.313-2.882-3.58-3.005zM9.32
|
||||||
|
7.894c-1.99-.336-3.646.846-3.58 3.004.066.828
|
||||||
|
4.313-2.881 3.58-3.004zM12.178
|
||||||
|
7.39c-1.187-.03-2.327.882-2.33 1.411-.003.643.939 1.302
|
||||||
|
2.339 1.318 1.429.01 2.34-.527
|
||||||
|
2.345-1.19.006-.752-1.3-1.55-2.354-1.539zM12.251
|
||||||
|
20.578c1.036-.045 2.425.333 2.428.836.017.488-1.26
|
||||||
|
1.59-2.497
|
||||||
|
1.569-1.28.055-2.536-1.05-2.52-1.432-.019-.56 1.56-.999
|
||||||
|
2.589-.973zM8.426 17.6c.737.888 1.073 2.449.458
|
||||||
|
2.909-.582.351-1.996.207-3-1.237-.678-1.211-.591-2.444-.115-2.806.711-.433
|
||||||
|
1.81.152 2.657 1.134zM15.929 17.318c-.798.935-1.242
|
||||||
|
2.64-.66 3.188.556.427 2.05.367
|
||||||
|
3.153-1.164.801-1.028.533-2.746.075-3.201-.68-.526-1.656.147-2.568
|
||||||
|
1.177z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span>Raspberry Pi</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-list">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Features</h2>
|
||||||
|
<div class="feature-badges">
|
||||||
|
<div class="feature-badge">
|
||||||
|
<img src="static-site-assets/images/lock.svg" />
|
||||||
|
<div>
|
||||||
|
Secure https for all tunnels
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-badge">
|
||||||
|
<img src="static-site-assets/images/computer.svg" />
|
||||||
|
<div>
|
||||||
|
Show your work to anyone
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-badge">
|
||||||
|
<img src="static-site-assets/images/language.svg" />
|
||||||
|
<div>
|
||||||
|
Test API Webhooks
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature-badge">
|
||||||
|
<img src="static-site-assets/images/cloud.svg" />
|
||||||
|
<div>
|
||||||
|
Test your UI in cloud browsers
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="donate-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Donate and become a sponsor of a more open web</h2>
|
||||||
|
<p>We're on a mission to build a more open web. Telebit is still in it's
|
||||||
|
early days and the development is supported by generous sponsors like
|
||||||
|
you. Make a recurring or one-time donation today.
|
||||||
|
</p>
|
||||||
|
<a class="link-button" target="_blank" href="https://www.patreon.com/coolaj86">Make a donation</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mailing-list-form">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Join our mailing list</h2>
|
||||||
|
<form class="js-inline-email-form email-signup-form" novalidate>
|
||||||
|
<div class="form-error js-inactive"></div>
|
||||||
|
<div class="success-message js-inactive">Thank you for joining!</div>
|
||||||
|
<span class="input-container email">
|
||||||
|
<div class="input-error email js-inactive"></div>
|
||||||
|
<input type="email" name="email" id="email" placeholder="Email">
|
||||||
|
</span>
|
||||||
|
<input class="link-button" type="submit" value="Join">
|
||||||
|
</form>
|
||||||
|
<ul>
|
||||||
|
<li><img src="static-site-assets/images/done.svg" />Get exclusive invites to try new features</li>
|
||||||
|
<li><img src="static-site-assets/images/done.svg" />Get updates on our progress</li>
|
||||||
|
<li><img src="static-site-assets/images/done.svg" />We'll never spam you</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
Telebit
|
||||||
|
</div>
|
||||||
|
<ul clss="footer-links">
|
||||||
|
<li><a href="">Privacy</a></li>
|
||||||
|
<li><a href="">Terms</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
<script src="./static-site-assets/scripts/form-processing.js"></script>
|
||||||
|
</html>
|
|
@ -0,0 +1,215 @@
|
||||||
|
/*global Vue*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var OAUTH3 = window.OAUTH3;
|
||||||
|
var oauth3 = OAUTH3.create({
|
||||||
|
host: window.location.host
|
||||||
|
, pathname: window.location.pathname.replace(/\/[^\/]*$/, '/')
|
||||||
|
});
|
||||||
|
//var $ = function () { return document.querySelector.apply(document, arguments); };
|
||||||
|
var sessionStr = localStorage.getItem('session');
|
||||||
|
var session;
|
||||||
|
if (sessionStr) {
|
||||||
|
try {
|
||||||
|
session = JSON.parse(sessionStr);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsRecords = {
|
||||||
|
"telebit.ppl.family": "178.128.3.196"
|
||||||
|
, "telebit.cloud": "46.101.97.218"
|
||||||
|
};
|
||||||
|
var vueData = {
|
||||||
|
claims: []
|
||||||
|
, domains: []
|
||||||
|
, devices: []
|
||||||
|
, newDomain: null
|
||||||
|
, newDomainWildcard: false
|
||||||
|
, newEmail: null
|
||||||
|
, hasAccount: false
|
||||||
|
, token: null
|
||||||
|
, spinner: false
|
||||||
|
, site: { deviceDomain: document.location.hostname, deviceDomainA: dnsRecords[document.location.hostname] }
|
||||||
|
};
|
||||||
|
var vueMethods = {
|
||||||
|
challengeDns: function () {
|
||||||
|
// we could do a checkbox
|
||||||
|
var wildcard = vueData.newDomainWildcard || false;
|
||||||
|
if (!/(\*\.)?[a-z0-9][a-z0-9\.\-_]\.[a-z0-9]{2,}/.test(vueData.newDomain)) {
|
||||||
|
window.alert("invalid domain name '" + vueData.newDomain + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// we can just detect by text
|
||||||
|
if ('*.' === vueData.newDomain.slice(0, 2)) {
|
||||||
|
vueData.newDomain = vueData.newDomain.slice(2);
|
||||||
|
wildcard = true;
|
||||||
|
}
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new'
|
||||||
|
, method: 'POST'
|
||||||
|
, session: session
|
||||||
|
, data: { type: 'dns', value: vueData.newDomain, wildcard: wildcard }
|
||||||
|
}).then(function (resp) {
|
||||||
|
vueData.claims.unshift(resp.data.claim);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, checkDns: function (claim) {
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new/:value/:challenge'
|
||||||
|
.replace(/:value/g, claim.value)
|
||||||
|
.replace(/:challenge/g, claim.challenge)
|
||||||
|
, method: 'POST'
|
||||||
|
, session: session
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, challengeEmail: function () {
|
||||||
|
console.log("A new (Email) challenger!", vueData);
|
||||||
|
}
|
||||||
|
, pushDomain: function (dev) {
|
||||||
|
// TODO do some local validation too
|
||||||
|
console.log('pushDomain', dev);
|
||||||
|
vueData.spinner = true;
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/devices/:dev/:name'
|
||||||
|
.replace(/:dev/g, dev.socketId)
|
||||||
|
.replace(/:name/g, dev.newDomain)
|
||||||
|
, method: 'POST'
|
||||||
|
, session: session
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
|
window.alert(err.toString());
|
||||||
|
}).then(function () {
|
||||||
|
dev.newDomain = '';
|
||||||
|
vueData.spinner = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, login: function () {
|
||||||
|
var email = vueData.newEmail;
|
||||||
|
vueMethods.logout();
|
||||||
|
onClickLogin(email);
|
||||||
|
}
|
||||||
|
, logout: function () {
|
||||||
|
sessionStorage.clear();
|
||||||
|
vueData.hasAccount = false;
|
||||||
|
// TODO OAUTH3._logoutHelper(directives, opts)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var app = new Vue({
|
||||||
|
el: '.v-app'
|
||||||
|
, data: vueData
|
||||||
|
, methods: vueMethods
|
||||||
|
});
|
||||||
|
app = null;
|
||||||
|
|
||||||
|
function listStuff(data) {
|
||||||
|
//window.alert("TODO: show authorized devices, domains, and connectivity information");
|
||||||
|
vueData.hasAccount = true;
|
||||||
|
vueData.domains = data.domains;
|
||||||
|
vueData.devices = data.devices;
|
||||||
|
vueData.claims = data.claims;
|
||||||
|
}
|
||||||
|
function loadAccount(session) {
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account'
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
|
||||||
|
console.info("Telebit Account:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
if (resp.data && resp.data.domains) {
|
||||||
|
listStuff(resp.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === resp.data.accounts.length) {
|
||||||
|
listStuff(resp);
|
||||||
|
} else if (0 === resp.data.accounts.length) {
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + 'api/telebit.cloud/account'
|
||||||
|
, method: 'POST'
|
||||||
|
, session: session
|
||||||
|
, body: {
|
||||||
|
email: vueData.newEmail
|
||||||
|
}
|
||||||
|
}).then(function (resp) {
|
||||||
|
listStuff(resp);
|
||||||
|
});
|
||||||
|
} if (resp.data.accounts.length > 2) {
|
||||||
|
window.alert("Multiple accounts.");
|
||||||
|
} else {
|
||||||
|
window.alert("Bad response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeProvider(providerUri) {
|
||||||
|
// example https://oauth3.org
|
||||||
|
return oauth3.setIdentityProvider(providerUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This opens up the login window for the specified provider
|
||||||
|
//
|
||||||
|
function onClickLogin(email) {
|
||||||
|
// TODO check subject for provider viability
|
||||||
|
vueData.spinner = true;
|
||||||
|
return oauth3.authenticate({
|
||||||
|
subject: email
|
||||||
|
, scope: 'email@oauth3.org'
|
||||||
|
}).then(function (session) {
|
||||||
|
|
||||||
|
console.info('Authentication was Successful:');
|
||||||
|
console.log(session);
|
||||||
|
|
||||||
|
// You can use the PPID (or preferably a hash of it) as the login for your app
|
||||||
|
// (it securely functions as both username and password which is known only by your app)
|
||||||
|
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
||||||
|
//
|
||||||
|
console.info('Secure PPID (aka subject):', session.token.sub);
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
|
||||||
|
.replace(/:sub/g, session.token.sub)
|
||||||
|
.replace(/:kid/g, session.token.iss)
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
console.info("Public Key:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.oauth3.org/api/issuer@oauth3.org/acl/profile'
|
||||||
|
, session: session
|
||||||
|
}).then(function (resp) {
|
||||||
|
|
||||||
|
console.info("Inspect Token:");
|
||||||
|
console.log(resp.data);
|
||||||
|
|
||||||
|
localStorage.setItem('session', JSON.stringify(session));
|
||||||
|
loadAccount(session);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}, function (err) {
|
||||||
|
console.error('Authentication Failed:');
|
||||||
|
console.log(err);
|
||||||
|
}).then(function () {
|
||||||
|
vueData.spinner = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//$('body form.js-auth-form').addEventListener('submit', function onClickLoginHelper(ev) {
|
||||||
|
// ev.preventDefault();
|
||||||
|
// ev.stopPropagation();
|
||||||
|
// var email = $('.js-auth-subject').value;
|
||||||
|
// onClickLogin(email);
|
||||||
|
//});
|
||||||
|
onChangeProvider('oauth3.org');
|
||||||
|
if (session) {
|
||||||
|
vueData.token = session.access_token;
|
||||||
|
loadAccount(session);
|
||||||
|
}
|
||||||
|
}());
|
|
@ -0,0 +1,65 @@
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
document.body.hidden = false;
|
||||||
|
|
||||||
|
function formSubmit() {
|
||||||
|
// to be used for good, not evil
|
||||||
|
var msg = {
|
||||||
|
name: document.querySelector('.js-list-comment').value
|
||||||
|
, address: document.querySelector('.js-list-address').value
|
||||||
|
, list: 'telebit@ppl.family'
|
||||||
|
};
|
||||||
|
|
||||||
|
window.fetch('https://api.ppl.family/api/ppl.family/public/list', {
|
||||||
|
method: 'POST'
|
||||||
|
, cors: true
|
||||||
|
, headers: new Headers({ 'Content-Type': 'application/json' })
|
||||||
|
, body: JSON.stringify(msg)
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json().then(function (data) {
|
||||||
|
if (data.error) {
|
||||||
|
window.alert("Couldn't save your message. Email coolaj86@gmail.com instead.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.querySelector('.js-list-form').hidden = true;
|
||||||
|
document.querySelector('.js-list-form').className += ' hidden';
|
||||||
|
document.querySelector('.js-list-thanks').hidden = false;
|
||||||
|
document.querySelector('.js-list-thanks').className = document.querySelector('.js-list-thanks').className.replace(/\s*hidden\b/, '');
|
||||||
|
}, function () {
|
||||||
|
window.alert("Couldn't save your message. Email coolaj86@gmail.com instead.");
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
window.alert("Didn't get your message. Bad network connection? Email coolaj86@gmail.com instead.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.body.addEventListener('submit', function (ev) {
|
||||||
|
if (ev.target.matches('.js-list-form')) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
formSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.body.addEventListener('click', function (ev) {
|
||||||
|
if (ev.target.matches('.js-list-submit')) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
formSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (ev.target.closest('.js-navbar-toggle')) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (/show/.test(document.querySelector('.js-navbar-collapse').className)) {
|
||||||
|
document.querySelector('.js-navbar-collapse').className = document.querySelector('.js-navbar-collapse').className.replace(/\s+show\b/, '');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.js-navbar-collapse').className += ' show';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
|
@ -0,0 +1,28 @@
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Terms of Service</h1>
|
||||||
|
<p>To be used for good, not evil.</p>
|
||||||
|
|
||||||
|
<h1>Privacy</h1>
|
||||||
|
<p>We'll keep your info to ourselves.</p>
|
||||||
|
|
||||||
|
<h1>License</h1>
|
||||||
|
<p>There are Commercial and Open Source versions of Telebit<br>(kinda like how Google has Chrome and Chromium).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Open Source versions are available as
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/telebit.js" target="_blank">Telebit Remote</a> (the "client" daemon)</li>
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/telebit.js" target="_blank">Telebit Relay</a> (the service daemon)</li>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Trademark</h1>
|
||||||
|
<p>Telebit is a trademark of AJ ONeal
|
||||||
|
</p>
|
||||||
|
</body>
|
|
@ -0,0 +1,228 @@
|
||||||
|
body {
|
||||||
|
font-family: Source Sans Pro, sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
letter-spacing: -0.022222222em;
|
||||||
|
line-height: 1.33;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 4em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.777777778em;
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.333333333em;
|
||||||
|
height: 1.333333333em;
|
||||||
|
fill: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.icon-computer {width: 4em;height: 4em;}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
border: none;
|
||||||
|
font-size: 1em;
|
||||||
|
color: white;
|
||||||
|
padding: 0.44444em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.444444444em 0.888889em;
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0.888888889em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
width: 17.777777778em;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox] ~ .icon-checked-box {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox] ~ .icon-unchecked-box {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox]:checked ~ .icon-checked-box {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox]:checked ~ .icon-unchecked-box {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array input[type=checkbox]:focus ~ .icon-checked-box, .checkbox-array input[type=checkbox]:focus ~ .icon-unchecked-box {
|
||||||
|
background: #DDDDDD;
|
||||||
|
}
|
||||||
|
.checkbox-array .icon-checked-box, .checkbox-array .icon-unchecked-box {
|
||||||
|
margin-right: 0.666666667em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-array label {
|
||||||
|
display: flex;
|
||||||
|
height: 1.333333333em;
|
||||||
|
font-size: 0.833333333em;
|
||||||
|
margin: 0.4em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1.logo {
|
||||||
|
font-size: 1.555555556em;
|
||||||
|
margin-bottom: 1.777777778em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
svg.authorized-check {
|
||||||
|
fill: #63f794;
|
||||||
|
margin-right: 0.666666667em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress .row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
margin: 0 0 0.6666em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-ball {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #1a1a1a;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.spinner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 0.666666667em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.important-text {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 1.111177778em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-info-container {
|
||||||
|
text-align: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
/* overflow: hidden; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-info-container pre {
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-info {
|
||||||
|
max-width: 65em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.debugging.button {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.js-debugging-button.debugging-button {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-button {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0.3em;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: white;
|
||||||
|
border: solid #eee 1px;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-info-container.visible .debugging-button svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-button svg {transition: transform 0.3s;}
|
||||||
|
|
||||||
|
.debug-drawer {
|
||||||
|
/* position: relative; */
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
padding: 0.1em 0;
|
||||||
|
background: white;
|
||||||
|
pointer-events: initial;
|
||||||
|
border-top: solid #eee 1px;
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.debugging-info-container.visible .debug-drawer {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugging-info-container {
|
||||||
|
padding-top: 3em;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner .spinner-ball {
|
||||||
|
animation: pulsing 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner .spinner-ball:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
.spinner .spinner-ball:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
@keyframes pulsing {
|
||||||
|
0% {transform: scale(1);}
|
||||||
|
35% {transform: scale(1);}
|
||||||
|
60% {transform: scale(1.3);}
|
||||||
|
75% {transform: scale(1.3);}
|
||||||
|
100% {transform: scale(1);}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.finish-button {
|
||||||
|
margin-top: 2.222222222em;
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Telebit - Pair Device</title>
|
||||||
|
<link href="./css/main.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: block;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans Pro';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: block;
|
||||||
|
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="preload" href="/static-site-assets/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<link rel="preload" href="/static-site-assets/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>document.body.hidden = true;</script>
|
||||||
|
<!-- let's define our SVG that we will use later -->
|
||||||
|
<svg width="0" height="0" viewBox="0 0 24 24">
|
||||||
|
<defs>
|
||||||
|
<g id="svg-check">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-checked">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-unchecked">
|
||||||
|
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-download">
|
||||||
|
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-computer">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-circle-check">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||||
|
</g>
|
||||||
|
<g id="svg-arrow-down">
|
||||||
|
<path d="M7.41,8.59L12,13.17l4.59-4.58L18,10l-6,6l-6-6L7.41,8.59z"/>
|
||||||
|
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="js-error" hidden>
|
||||||
|
<h1>Invalid Pairing Link</h1>
|
||||||
|
<div class="js-magic-link">'{{magic_link}}' isn't a valid pairing link code.
|
||||||
|
<br>Links are only valid for a limited time, so you gotta act fast.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container js-magic" hidden><form class="js-submit">
|
||||||
|
<h1 class="logo">Telebit</h1>
|
||||||
|
<svg class="icon-computer" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-computer"></use>
|
||||||
|
</svg>
|
||||||
|
<h2>Pair <span class="js-hostname">Device</span></h1>
|
||||||
|
<label><span class="important-text">Enter your device pairing code</span>
|
||||||
|
<input type="text" name="pair-code" placeholder="ex: 0000" autofocus>
|
||||||
|
</label>
|
||||||
|
<div class="checkbox-array">
|
||||||
|
<label>
|
||||||
|
<input name="telebit-agree" type="checkbox" required>
|
||||||
|
<svg class="icon-checked-box" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-checked"></use>
|
||||||
|
</svg>
|
||||||
|
<svg class="icon-unchecked-box" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-unchecked"></use>
|
||||||
|
</svg>
|
||||||
|
<div>Agree to <a target="_blank" href="/legal/">Telebit™ Terms of Service</a></div>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input name="letsencrypt-agree" type="checkbox" required>
|
||||||
|
<svg class="icon-checked-box" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-checked"></use>
|
||||||
|
</svg>
|
||||||
|
<svg class="icon-unchecked-box" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-unchecked"></use>
|
||||||
|
</svg>
|
||||||
|
<div>Agree to <a target="_blank" href="https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"> Let's Encrypt™ Terms of Service</a></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" disabled>Claim Device</button>
|
||||||
|
</div>
|
||||||
|
</form></div>
|
||||||
|
<div class="container js-authz" hidden>
|
||||||
|
<h1 class="logo">Telebit</h1>
|
||||||
|
<svg class="icon-computer" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-computer"></use>
|
||||||
|
</svg>
|
||||||
|
<h2>Pair <span class="js-hostname">Device</span></h1>
|
||||||
|
<div>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="row">
|
||||||
|
<svg class="authorized-check" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-circle-check"></use>
|
||||||
|
</svg>
|
||||||
|
Authorized
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="spinner">
|
||||||
|
<div class="spinner-ball ball-1"></div>
|
||||||
|
<div class="spinner-ball ball-1"></div>
|
||||||
|
<div class="spinner-ball ball-1"></div>
|
||||||
|
</span>
|
||||||
|
Waiting for device to pair
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="important-text">
|
||||||
|
Check the command line on your device to finish pairing.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="js-debug-container debugging-info-container" hidden>
|
||||||
|
<div class="debug-drawer">
|
||||||
|
<span class="js-debug-button debugging-button">
|
||||||
|
Debugging info <svg class="debugging-arrow" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-arrow-down"></use>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div class="js-debug-info debugging-info">
|
||||||
|
<p><a class="js-new-href">{{js-new-href}}</a></p>
|
||||||
|
<p class="js-serviceport">xxxxx</p>
|
||||||
|
<p><small>Authorization Token:
|
||||||
|
<pre><code class="js-token">{{js-token}}</code></pre></small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container js-finish" hidden>
|
||||||
|
<h1 class="logo">Telebit</h1>
|
||||||
|
<svg class="icon-computer" viewBox="0 0 24 24">
|
||||||
|
<use xlink:href="#svg-computer"></use>
|
||||||
|
</svg>
|
||||||
|
<h2>Success!</h1>
|
||||||
|
<div>
|
||||||
|
<span class="important-text js-new-domain">______</span> is paired and ready to use for accessing your device and sharing your stuff.
|
||||||
|
</div>
|
||||||
|
<button class="js-finish-button finish-button">Take Me There</button>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,180 @@
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var meta = {};
|
||||||
|
var magic;
|
||||||
|
var domainname;
|
||||||
|
var port;
|
||||||
|
|
||||||
|
function checkStatus() {
|
||||||
|
// TODO use Location or Link
|
||||||
|
window.fetch(meta.baseUrl + 'api/telebit.cloud/pair_state/' + magic, {
|
||||||
|
method: 'GET'
|
||||||
|
, cors: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json().then(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
if ('invalid' === data.status) {
|
||||||
|
window.alert("something went wrong");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('complete' === data.status) {
|
||||||
|
successScreen();
|
||||||
|
setTimeout(function () {
|
||||||
|
//window.document.body.innerHTML += ('<img src="https://' + domainname + '/_apis/telebit.cloud/clear.gif">');
|
||||||
|
// TODO once this is loaded (even error) Let's Encrypt is done,
|
||||||
|
// then it's time to redirect to the domain. Yay!
|
||||||
|
}, 1 * 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(checkStatus, 2 * 1000);
|
||||||
|
}, function (err) {
|
||||||
|
console.error(err);
|
||||||
|
setTimeout(checkStatus, 2 * 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function successScreen() {
|
||||||
|
document.querySelector('.js-authz').hidden = true;
|
||||||
|
document.querySelector('.js-finish-button').addEventListener('click', function(e) {
|
||||||
|
window.location.href='https://' + domainname + "/#/serviceport=" + port;
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.js-new-domain').forEach(function(ele) {
|
||||||
|
ele.innerHTML = domainname;
|
||||||
|
});
|
||||||
|
document.querySelector('.js-finish').hidden = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitCode(pair) {
|
||||||
|
// TODO use Location or Link
|
||||||
|
document.querySelector('.js-magic').hidden = true;
|
||||||
|
window.fetch(meta.baseUrl + 'api/telebit.cloud/pair_code/', {
|
||||||
|
method: 'POST'
|
||||||
|
, headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
, body: JSON.stringify({
|
||||||
|
magic: pair.magic
|
||||||
|
, pin: pair.pin || pair.code
|
||||||
|
, agree_tos: pair.agreeTos
|
||||||
|
})
|
||||||
|
, cors: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json().then(function (data) {
|
||||||
|
// TODO check for error (i.e. bad Pair Code / PIN)
|
||||||
|
// shouldn't be pending (because we get here by being ready)
|
||||||
|
// should poll over 'ready'
|
||||||
|
|
||||||
|
console.log('Submit Code Response:');
|
||||||
|
console.log(data);
|
||||||
|
if (data.error) {
|
||||||
|
document.querySelector('.js-error').hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(checkStatus, 0);
|
||||||
|
|
||||||
|
document.querySelector('.js-authz').hidden = false;
|
||||||
|
document.querySelector('.js-debug-container').hidden = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
document.querySelectorAll('.js-token-data').forEach(function ($el) {
|
||||||
|
$el.innerText = JSON.stringify(data, null, 2);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
document.querySelectorAll('.js-new-href').forEach(function ($el) {
|
||||||
|
domainname = data.domains[0];
|
||||||
|
port = data.port;
|
||||||
|
$el.href = 'https://' + data.domains[0] + '/';
|
||||||
|
$el.innerText = '🔐 https://' + data.domains[0];
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.js-domainname').forEach(function ($el) {
|
||||||
|
$el.innerText = data.domains.join(',');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.js-serviceport').forEach(function ($el) {
|
||||||
|
$el.innerText = data.ports.join(',');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.js-token').forEach(function ($el) {
|
||||||
|
$el.innerText = data.jwt;
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
console.error(err);
|
||||||
|
document.querySelector('.js-error').hidden = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
magic = (window.location.hash || '').substr(2).replace(/magic=/, '');
|
||||||
|
|
||||||
|
if (!magic) {
|
||||||
|
document.querySelector('body').hidden = false;
|
||||||
|
document.querySelector('.js-error').hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fetch(meta.baseUrl + meta.pair_request.pathname + '/' + magic, {
|
||||||
|
method: 'GET'
|
||||||
|
, cors: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json().then(function (data) {
|
||||||
|
console.log('pair request data:');
|
||||||
|
console.log(data);
|
||||||
|
document.querySelector('body').hidden = false;
|
||||||
|
if (data.error) {
|
||||||
|
document.querySelector('.js-error').hidden = false;
|
||||||
|
document.querySelector('.js-magic-link').innerText = "Something went wrong. Perhaps an bad or expired link.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.querySelector('.js-magic').hidden = false;
|
||||||
|
document.querySelectorAll('.js-hostname').forEach(function(ele) {
|
||||||
|
ele.innerText = data.hostname || 'Device';
|
||||||
|
});
|
||||||
|
//document.querySelector('.js-token-data').innerText = JSON.stringify(data, null, 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.js-submit').addEventListener('submit', function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var pair = {};
|
||||||
|
pair.magic = magic;
|
||||||
|
pair.code = document.querySelector('[name=pair-code]').value;
|
||||||
|
pair.agreeTos = document.querySelector('[name=letsencrypt-agree]').checked
|
||||||
|
&& document.querySelector('[name=telebit-agree]').checked;
|
||||||
|
console.log('Pair Form:');
|
||||||
|
console.log(pair);
|
||||||
|
submitCode(pair);
|
||||||
|
});
|
||||||
|
|
||||||
|
var formElements = document.querySelector('.js-submit').elements;
|
||||||
|
for(var i = 0; i < formElements.length; ++i) {
|
||||||
|
var tosCheck = document.querySelector('[name=telebit-agree]');
|
||||||
|
var leCheck = document.querySelector('[name=letsencrypt-agree]');
|
||||||
|
var pairCodeInput = document.querySelector('[name=pair-code]');
|
||||||
|
formElements[i].addEventListener('input', function(ev) {
|
||||||
|
if(tosCheck.checked && leCheck.checked && pairCodeInput.value.length) {
|
||||||
|
document.querySelector('.js-submit button').disabled = false;
|
||||||
|
} else {
|
||||||
|
document.querySelector('.js-submit button').disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
document.querySelector('.js-debug-button').addEventListener("click", function(e) {
|
||||||
|
document.querySelector('.js-debug-container').classList.toggle("visible");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fetch('https://' + location.hostname + '/_apis/telebit.cloud/index.json', {
|
||||||
|
method: 'GET'
|
||||||
|
, cors: true
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json().then(function (_json) {
|
||||||
|
meta = _json;
|
||||||
|
meta.baseUrl = 'https://' + meta.api_host.replace(/:hostname/g, location.hostname) + '/';
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Optify - Containerize without the container</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Optify</h1>
|
||||||
|
<p>containerize without the container</p>
|
||||||
|
|
||||||
|
<p>Holds your hand without being so hands on.
|
||||||
|
Like brew and Docker had a baby that <em>did</em> care about versions and <em>didn't</em> care about port numbers.
|
||||||
|
Only for the masters of their domain.
|
||||||
|
Be wise and be bold.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#<pre><code>
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# minimal os detection
|
||||||
|
my_os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||||
|
|
||||||
|
# https://github.com/golang/go/wiki/GoArm#supported-architectures
|
||||||
|
# minimal cpu arch detection
|
||||||
|
my_arch="$(uname -m)"
|
||||||
|
if [ "x86_64" == "$my_arch" ]; then
|
||||||
|
my_arch="amd64"
|
||||||
|
elif [ "i386" == "$my_arch" ]; then
|
||||||
|
my_arch="386"
|
||||||
|
elif [ -n "$($my_arch | grep arm)" ]; then
|
||||||
|
if [ -n "$(uname -a | grep aarch64)" ]; then
|
||||||
|
my_arch="arm64"
|
||||||
|
elif [ -n "$(uname -a | grep armv8l)" ]; then
|
||||||
|
my_arch="arm64"
|
||||||
|
elif [ -n "$(uname -a | grep armv7l)" ]; then
|
||||||
|
my_arch="armv7l"
|
||||||
|
elif [ -n "$(uname -a | grep armv6l)" ]; then
|
||||||
|
my_arch="armv6l"
|
||||||
|
else
|
||||||
|
echo "could not determine arm cpu architecture" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "could not determine cpu architecture" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get optify for this cpu and arch
|
||||||
|
if [ -z "${OPTIFY_VERSION:-}" ]; then
|
||||||
|
latest_version="$(curl -fsSL https://telebit.cloud/optify/latest)"
|
||||||
|
OPTIFY_VERSION=$latest_version
|
||||||
|
echo "Installing optify-$OPTIFY_VERSION (latest)"
|
||||||
|
else
|
||||||
|
echo "Installing optify OPTIFY_VERSION=$OPTIFY_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# download to a tmp folder
|
||||||
|
#my_tmpdir="$(mktemp -d /tmp/optify.XXXXXXXX)"
|
||||||
|
my_tmpdir="$(mktemp -d -t optify.XXXXXXXX)"
|
||||||
|
my_url="https://telebit.cloud/optify/dist/${my_os}/${my_arch}/optify-${OPTIFY_VERSION}"
|
||||||
|
if [ -n "$(type -p curl || true)" ]; then
|
||||||
|
my_out="$(curl -fsSL -o "$my_tmpdir/optify-${OPTIFY_VERSION}" "$my_url" || true)"
|
||||||
|
elif [ -n "$(type -p wget || true)" ]; then
|
||||||
|
my_out="$(wget -q -c "$my_url" -O "$my_tmpdir/optify-${OPTIFY_VERSION}" || true)"
|
||||||
|
else
|
||||||
|
echo "found neither wget nor curl" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check for downloader success
|
||||||
|
chmod a+x "$my_tmpdir/optify-${OPTIFY_VERSION}"
|
||||||
|
"$my_tmpdir/optify-${OPTIFY_VERSION}" --install
|
|
@ -0,0 +1 @@
|
||||||
|
v0.0.5
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>sclient - tls unwrapper for Windows, Mac, and Linux</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>sclient</h1>
|
||||||
|
<p>ssl unwrapper for Windows, Mac, and Linux</p>
|
||||||
|
<p>a poor man's alternative to <code>openssl s_client</code>, <code>stunnel</code>, <code>socat</code>
|
||||||
|
for the simple use case of connecting a client application that doesn't support tls+sni
|
||||||
|
through a secure connection, https proxy, or sni multiplexer
|
||||||
|
(think <code>telnet</code>, <code>netcat</code>, <code>ssh</code>, <code>openvpn</code>, etc).</p>
|
||||||
|
|
||||||
|
<h2>Usage</h2>
|
||||||
|
<pre><code>$ sclient [flags] <remote> <local></code></pre>
|
||||||
|
<pre><code>$ sclient example.com:443 localhost:3000</code></pre>
|
||||||
|
<h3>Flags</h3>
|
||||||
|
<ul>
|
||||||
|
<li><kbd>-k, --insecure</kbd> ignore invalid tls certificates</li>
|
||||||
|
<li><kbd>--servername <string></kbd> spoof SNI
|
||||||
|
(to disable use IP as <remote> and do not use this option)</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Arguments</h3>
|
||||||
|
<ul>
|
||||||
|
<li><kbd><remote></kbd> the servername and port of the tls-enabled server (default port is 443)</li>
|
||||||
|
<li><kbd><local></kbd> the local address and port to bind to (default bind address is 127.0.0.1 or ::1)
|
||||||
|
<ul>
|
||||||
|
<li><code>-</code> may be used to read from stdin (like netcat)</li>
|
||||||
|
<li>may be omitted when piping (see pipe example below)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Examples</h2>
|
||||||
|
<h3>SSH</h3>
|
||||||
|
<pre><code>$ ssh -o ProxyCommand="sclient %h" jon.telebit.io</code></pre>
|
||||||
|
|
||||||
|
<p>This is useful to be able to connect to SSH even from behind a corporate packet-inspection firewall.
|
||||||
|
It can also be used to multiplex and relay multiple ssh connections through a single host.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Telnet </h3>
|
||||||
|
<pre><code>$ sclient example.com:443 localhost:3000
|
||||||
|
> [listening] example.com:443 <= localhost:3000</code></pre>
|
||||||
|
<pre><code>$ telnet localhost 3000</code></pre>
|
||||||
|
<h3>stdin/stdout</h3>
|
||||||
|
<pre><code>$ sclient whatever.com -
|
||||||
|
> (connected to whatever.com:443 and reading from stdin)</code></pre>
|
||||||
|
Use just like netcat or telnet. A manual HTTP request, for example:
|
||||||
|
<pre><code>> GET / HTTP/1.1
|
||||||
|
> Host: whatever.com
|
||||||
|
> Connection: close
|
||||||
|
>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>pipe</h3>
|
||||||
|
<pre><code>$ printf "GET / HTTP/1.1\r\nHost: telebit.cloud\r\n\r\n" | sclient telebit.cloud</code></pre>
|
||||||
|
|
||||||
|
<h2>Downloads (standalone) <small>v1.2</small></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Windows 7/8/10
|
||||||
|
<a href="dist/windows/amd64/sclient.exe">Download</a>
|
||||||
|
| <a href="dist/windows/386/sclient.exe">(x86)</a>
|
||||||
|
</li>
|
||||||
|
<li>macOS / OS X / Darwin
|
||||||
|
<a href="dist/darwin/amd64/sclient">Download</a>
|
||||||
|
</li>
|
||||||
|
<li>Linux Ubuntu/Arch/etc
|
||||||
|
<a href="dist/linux/amd64/sclient">Download</a>
|
||||||
|
| <a href="dist/linux/386/sclient">(386)</a>
|
||||||
|
</li>
|
||||||
|
<li>Raspberry Pi
|
||||||
|
<a href="dist/linux/armv7/sclient">Download</a>
|
||||||
|
| <a href="dist/linux/armv5/sclient">(Pi Zero)</a>
|
||||||
|
</li>
|
||||||
|
<li>Source Code
|
||||||
|
<a href="https://git.coolaj86.com/coolaj86/sclient.go">Go (golang)</a>
|
||||||
|
| <a href="https://git.coolaj86.com/coolaj86/sclient.js">node.js</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Source Code</h2>
|
||||||
|
<p><code>sclient</code> is maintained simultaneously in go and node.js</p>
|
||||||
|
<ul>
|
||||||
|
<li>Go
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/sclient.go">git</a></li>
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/sclient.go/archive/master.zip">zip</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
<li>node.js
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/sclient.js">git</a></li>
|
||||||
|
<li><a href="https://git.coolaj86.com/coolaj86/sclient.js/archive/master.zip">zip</a></li>
|
||||||
|
<li><kbd>npm install -g sclient</kbd></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Telebit Relay</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
[TODO: Setup Interface]
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li>Admin Server Name</li>
|
||||||
|
<li>Administrator Email</li>
|
||||||
|
<li>SSL ToS Agree</li>
|
||||||
|
<li>Community Member</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="170px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 170" version="1.1" height="170px">
|
||||||
|
<path d="m150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.197-2.12-9.973-3.17-14.34-3.17-4.58 0-9.492 1.05-14.746 3.17-5.262 2.13-9.501 3.24-12.742 3.35-4.929 0.21-9.842-1.96-14.746-6.52-3.13-2.73-7.045-7.41-11.735-14.04-5.032-7.08-9.169-15.29-12.41-24.65-3.471-10.11-5.211-19.9-5.211-29.378 0-10.857 2.346-20.221 7.045-28.068 3.693-6.303 8.606-11.275 14.755-14.925s12.793-5.51 19.948-5.629c3.915 0 9.049 1.211 15.429 3.591 6.362 2.388 10.447 3.599 12.238 3.599 1.339 0 5.877-1.416 13.57-4.239 7.275-2.618 13.415-3.702 18.445-3.275 13.63 1.1 23.87 6.473 30.68 16.153-12.19 7.386-18.22 17.731-18.1 31.002 0.11 10.337 3.86 18.939 11.23 25.769 3.34 3.17 7.07 5.62 11.22 7.36-0.9 2.61-1.85 5.11-2.86 7.51zm-31.26-123.01c0 8.1021-2.96 15.667-8.86 22.669-7.12 8.324-15.732 13.134-25.071 12.375-0.119-0.972-0.188-1.995-0.188-3.07 0-7.778 3.386-16.102 9.399-22.908 3.002-3.446 6.82-6.3113 11.45-8.597 4.62-2.2516 8.99-3.4968 13.1-3.71 0.12 1.0831 0.17 2.1663 0.17 3.2409z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 302 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 261 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 954 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 959 B |
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,9 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M0 0h24v24H0z"/>
|
||||||
|
<g transform="translate(1 2)">
|
||||||
|
<rect width="22" height="20" fill="#000" rx="1"/>
|
||||||
|
<path fill="#FFF" d="M6.495 3.942v1.125l-4.12 1.566V5.551l2.882-1.047-2.882-1.056V2.375l4.12 1.567zm.32 3.592h4.327v.779H6.814v-.78z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 436 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 375 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M0 0h24v24H0z"/>
|
||||||
|
<path fill="#1A1A1A" d="M21.41 8.22c-1.667 1.179-2.5 2.597-2.5 4.254 0 1.986 1.03 3.509 3.09 4.57-.553 1.6-1.354 2.993-2.402 4.178C18.549 22.407 17.592 23 16.726 23c-.408 0-.965-.135-1.67-.404l-.34-.13c-.69-.27-1.302-.404-1.834-.404-.502 0-1.052.105-1.649.316l-.426.153-.535.218c-.422.167-.848.251-1.277.251-1.012 0-2.13-.833-3.352-2.498C3.88 18.117 3 15.518 3 12.704c0-2 .55-3.61 1.649-4.832 1.1-1.222 2.555-1.833 4.368-1.833.677 0 1.31.124 1.9.371l.404.164.426.174c.378.16.684.24.917.24.298 0 .63-.069.993-.207l.557-.218.415-.153c.663-.24 1.394-.36 2.195-.36 1.9 0 3.429.724 4.586 2.17zM16.911 1c.022.255.033.45.033.589 0 1.258-.458 2.361-1.376 3.31-.917.95-1.983 1.424-3.199 1.424a5.474 5.474 0 0 1-.055-.611c0-1.069.426-2.072 1.278-3.01.852-.938 1.838-1.487 2.96-1.647.08-.015.2-.033.36-.055z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 982 B |
|
@ -0,0 +1,8 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M0 0h24v24H0z"/>
|
||||||
|
<g fill="#000" fill-rule="nonzero">
|
||||||
|
<path d="M6.502 3.124c2.152 1.11 3.403 2.007 4.088 2.771-.35 1.407-2.182 1.471-2.851 1.432.137-.064.251-.14.292-.258-.168-.12-.764-.012-1.18-.246.16-.033.235-.065.31-.183-.394-.125-.817-.233-1.066-.441.134.002.26.03.435-.092-.352-.19-.727-.34-1.019-.63.182-.004.378-.001.435-.068a3.924 3.924 0 0 1-.819-.665c.255.031.363.005.424-.04-.243-.25-.552-.46-.698-.767.189.065.362.09.486-.006-.082-.186-.437-.296-.641-.733.199.02.41.044.452 0-.092-.376-.25-.588-.406-.807.426-.006 1.071.002 1.042-.034l-.263-.27c.416-.112.841.018 1.15.115.14-.11-.002-.248-.171-.39.353.048.673.129.962.241.154-.14-.1-.279-.224-.418.547.104.778.25 1.008.395.167-.16.01-.296-.103-.435.412.153.624.35.848.544.075-.102.192-.177.051-.424.293.169.513.367.676.59.18-.115.108-.273.109-.418.304.247.497.51.733.767.047-.034.089-.152.126-.338.725.704 1.75 2.476.263 3.179-1.264-1.044-2.775-1.802-4.45-2.371zM17.921 3.124c-2.152 1.11-3.403 2.007-4.089 2.771.351 1.407 2.183 1.471 2.852 1.432-.137-.064-.251-.14-.292-.258.168-.12.764-.012 1.18-.246-.16-.033-.235-.065-.31-.183.393-.125.817-.233 1.066-.441-.135.002-.26.03-.436-.092.352-.19.728-.34 1.02-.63-.182-.004-.379-.001-.436-.068.323-.2.594-.422.82-.665-.255.031-.363.005-.424-.04.243-.25.551-.46.698-.767-.189.065-.362.09-.487-.006.083-.186.438-.296.642-.733-.2.02-.41.044-.453 0 .093-.376.251-.588.407-.807-.426-.006-1.071.002-1.042-.034l.263-.27c-.416-.112-.842.018-1.15.115-.14-.11.002-.248.171-.39a4.182 4.182 0 0 0-.962.241c-.154-.14.1-.279.223-.418-.546.104-.778.25-1.008.395-.166-.16-.01-.296.103-.435-.411.153-.624.35-.847.544-.076-.102-.192-.177-.052-.424a2.149 2.149 0 0 0-.675.59c-.181-.115-.108-.273-.109-.418-.304.247-.497.51-.733.767-.048-.034-.09-.152-.126-.338-.725.704-1.75 2.476-.263 3.179 1.264-1.044 2.775-1.802 4.449-2.371zM14.818 17.45c0 1.313-1.154 2.377-2.578 2.377s-2.578-1.064-2.578-2.377c0-1.313 1.154-2.377 2.578-2.377s2.578 1.064 2.578 2.377zM10.153 10.363c1.204.426 1.773 1.922 1.27 3.343-.501 1.42-1.884 2.227-3.088 1.801-1.204-.426-1.773-1.922-1.271-3.343.502-1.42 1.885-2.227 3.09-1.801zM14.226 10.236c-1.204.426-1.773 1.922-1.27 3.343.501 1.42 1.884 2.227 3.088 1.801 1.204-.425 1.773-1.922 1.271-3.342-.502-1.42-1.885-2.227-3.089-1.802zM5.41 11.803c1.153-.309.389 4.771-.55 4.355-1.032-.83-1.364-3.262.55-4.355zM18.737 11.74c-1.154-.309-.39 4.771.549 4.354 1.032-.83 1.364-3.261-.55-4.354zM14.818 7.957c1.99-.336 3.647.847 3.58 3.005-.066.827-4.313-2.882-3.58-3.005zM9.32 7.894c-1.99-.336-3.646.846-3.58 3.004.066.828 4.313-2.881 3.58-3.004zM12.178 7.39c-1.187-.03-2.327.882-2.33 1.411-.003.643.939 1.302 2.339 1.318 1.429.01 2.34-.527 2.345-1.19.006-.752-1.3-1.55-2.354-1.539zM12.251 20.578c1.036-.045 2.425.333 2.428.836.017.488-1.26 1.59-2.497 1.569-1.28.055-2.536-1.05-2.52-1.432-.019-.56 1.56-.999 2.589-.973zM8.426 17.6c.737.888 1.073 2.449.458 2.909-.582.351-1.996.207-3-1.237-.678-1.211-.591-2.444-.115-2.806.711-.433 1.81.152 2.657 1.134zM15.929 17.318c-.798.935-1.242 2.64-.66 3.188.556.427 2.05.367 3.153-1.164.801-1.028.533-2.746.075-3.201-.68-.526-1.656.147-2.568 1.177z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-labelledby="simpleicons-raspberrypi-icon" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title id="simpleicons-raspberrypi-icon">Raspberry Pi icon</title><path d="M16.111 17.338c-.857.989-1.334 2.79-.709 3.371.596.449 2.201.391 3.385-1.23.86-1.08.569-2.893.081-3.372-.73-.555-1.778.164-2.757 1.243v-.012zm-8.057.3c-.908-1.04-2.088-1.658-2.851-1.199-.51.382-.605 1.685.123 2.967 1.078 1.524 2.596 1.679 3.221 1.307.659-.488.3-2.137-.493-3.075zm4.105 3.145c-1.103-.026-2.798.439-2.776 1.032-.018.403 1.331 1.572 2.705 1.513 1.326.03 2.699-1.139 2.682-1.649-.004-.523-1.498-.927-2.607-.884l-.004-.012zm-.075-13.944c-1.275-.032-2.502.933-2.502 1.493-.004.68 1.008 1.376 2.51 1.394 1.543.01 2.518-.559 2.532-1.26.016-.794-1.394-1.639-2.518-1.627h-.022zm-3.071.532c-2.135-.345-3.913.9-3.842 3.192.07.884 4.63-3.041 3.843-3.177l-.001-.015zm9.749 3.251c.071-2.277-1.709-3.521-3.844-3.176-.787.135 3.772 4.061 3.844 3.176zm.364.824c-1.239-.329-.42 5.049.588 4.615 1.109-.869 1.466-3.446-.588-4.6v-.015zM4.228 16.121c1.007.45 1.827-4.929.589-4.6-2.053 1.153-1.698 3.73-.589 4.615v-.015zm9.415-5.948c-1.146.75-1.354 2.428-.461 3.746.891 1.318 2.543 1.813 3.691 1.078 1.146-.733 1.353-2.412.462-3.746-.892-1.333-2.545-1.813-3.692-1.063v-.015zm-3.096.135c-1.146-.734-2.799-.254-3.689 1.064-.892 1.334-.686 3.012.461 3.761s2.799.269 3.691-1.064c.885-1.318.675-2.997-.465-3.745l.002-.016zm4.369 7.162c-.009-1.393-1.252-2.518-2.781-2.502-1.527.016-2.761 1.139-2.754 2.532v.029c.01 1.394 1.254 2.517 2.783 2.502 1.527 0 2.756-1.138 2.742-2.517v-.029l.01-.015zm3.209-15.133c-2.307 1.184-3.652 2.128-4.389 2.938.377 1.498 2.344 1.558 3.063 1.512-.147-.06-.271-.149-.315-.269.18-.12.821-.016 1.268-.255-.171-.03-.252-.061-.329-.195.419-.135.875-.24 1.141-.465-.143 0-.278.03-.467-.09.377-.194.778-.359 1.095-.658-.196 0-.406 0-.466-.075.346-.21.635-.435.877-.704-.272.045-.39.016-.454-.03.261-.255.593-.479.749-.81-.203.076-.391.09-.522 0 .091-.194.47-.314.69-.779-.215.03-.441.046-.486 0 .098-.389.269-.613.435-.854-.457 0-1.15 0-1.117-.029l.283-.285c-.448-.12-.904.015-1.236.12-.149-.105 0-.255.185-.405-.39.061-.733.135-1.034.256-.164-.15.105-.285.24-.436-.599.12-.839.27-1.094.42-.18-.165-.015-.314.104-.449-.449.164-.674.374-.914.568-.09-.104-.209-.179-.06-.449-.314.18-.554.39-.734.629-.194-.134-.119-.299-.119-.449-.33.27-.54.54-.794.811-.061-.031-.105-.15-.135-.346-.779.75-1.889 2.623-.285 3.356 1.349-1.094 2.981-1.903 4.779-2.503l.041-.075zm-12.259 0c1.798.6 3.419 1.408 4.777 2.518 1.596-.75.493-2.623-.282-3.356-.041.194-.085.329-.135.359-.255-.27-.462-.54-.788-.81 0 .15.077.33-.117.45-.175-.239-.41-.45-.725-.63.149.256.025.33-.056.449-.24-.225-.465-.434-.899-.599.12.149.3.3.12.465-.239-.149-.494-.3-1.078-.42.135.149.404.3.238.45-.315-.122-.66-.212-1.035-.258.181.15.342.289.192.405-.345-.12-.806-.255-1.255-.135l.284.284c.03.037-.659.03-1.121.035.165.225.337.449.435.854-.045.045-.27.016-.483 0 .225.449.599.57.688.765-.135.096-.314.075-.523 0 .164.314.494.539.748.81-.074.044-.18.074-.464.037.239.26.524.494.869.704-.06.07-.271.069-.479.075.314.304.719.464 1.094.663-.195.136-.33.105-.465.105.255.225.72.329 1.139.464-.09.135-.164.165-.344.195.449.254 1.078.135 1.258.27-.045.119-.164.209-.314.27.719.045 2.697-.015 3.072-1.514-.736-.807-2.084-1.752-4.391-2.921l.04.016zM7.6.103c.236-.007.436.135.652.201.529-.17.65.063.91.159.577-.12.752.141 1.029.419l.322-.009c.869.507 1.305 1.536 1.457 2.065.152-.529.584-1.559 1.457-2.065l.321.007c.277-.283.453-.539 1.029-.418.261-.105.38-.33.911-.166.33-.104.62-.375 1.057-.045.368-.149.726-.195 1.045.09.495-.06.653.061.774.21.108 0 .809-.104 1.132.36.81-.09 1.064.464.774.988.165.255.337.494-.05.975.15.269.062.553-.27.913.091.374-.074.63-.374.839.06.51-.48.81-.629.914-.061.3-.181.584-.795.734-.089.449-.464.523-.824.614 1.185.675 2.188 1.558 2.188 3.731l.181.299c1.349.809 2.562 3.402.674 5.514-.119.659-.329 1.124-.511 1.648-.269 2.113-2.082 3.101-2.561 3.221-.689.525-1.438 1.02-2.442 1.363-.942.961-1.976 1.336-2.994 1.336h-.092c-1.033 0-2.063-.375-3.012-1.335-1.007-.344-1.754-.838-2.447-1.363-.479-.12-2.283-1.107-2.562-3.221-.187-.524-.394-1.004-.518-1.662-1.894-2.113-.681-4.705.666-5.515l.172-.3c0-2.172 1.005-3.057 2.188-3.73-.359-.09-.72-.165-.823-.615-.615-.15-.735-.434-.795-.734-.15-.105-.689-.404-.629-.928-.3-.211-.465-.465-.375-.854-.314-.346-.404-.645-.27-.915-.39-.479-.209-.733-.045-.974C3.236 1.329 3.491.76 4.3.85 4.614.385 5.32.491 5.423.491c.121-.15.285-.285.779-.225.314-.285.675-.24 1.049-.102.151-.12.286-.164.406-.164L7.6.103z"/></svg>
|
After Width: | Height: | Size: 4.4 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
|
||||||
|
<svg
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="766"
|
||||||
|
height="768"
|
||||||
|
viewBox="-2.61977004 -2.61977004 92.56520808 92.83416708"
|
||||||
|
id="svg8375">
|
||||||
|
<defs
|
||||||
|
id="defs8377" />
|
||||||
|
<path
|
||||||
|
d="M 0,12.40183 35.68737,7.5416 35.70297,41.96435 0.03321,42.16748 z m 35.67037,33.52906 0.0277,34.45332 -35.66989,-4.9041 -0.002,-29.77972 z M 39.99644,6.90595 87.31462,0 l 0,41.527 -47.31818,0.37565 z M 87.32567,46.25471 87.31457,87.59463 39.9964,80.91625 39.9301,46.17767 z"
|
||||||
|
id="path13" />
|
||||||
|
</svg>
|
||||||
|
<!-- version: 20110311, original size: 87.325668 87.594627, border: 3% -->
|
After Width: | Height: | Size: 861 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M0 0h24v24H0z"/>
|
||||||
|
<path fill="#000" fill-rule="nonzero" d="M2 4.819l8.11-1.105.004 7.823-8.107.047L2 4.819zm8.107 7.62l.006 7.83-8.107-1.114v-6.769l8.1.053zm.983-8.87L21.844 2v9.438l-10.754.085V3.57zm10.757 8.944l-.003 9.395L11.09 20.39l-.015-7.895 10.772.018z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 428 B |
|
@ -0,0 +1,149 @@
|
||||||
|
(function(){
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function validateFormData(data) {
|
||||||
|
var errors = {}
|
||||||
|
if(!data.address) {
|
||||||
|
errors.email = "Please enter an email address.";
|
||||||
|
} else if(data.address.length > 244) {
|
||||||
|
errors.email = "Email is too long.<br>" +
|
||||||
|
"If your email address is really this long, we apologize. <br>" +
|
||||||
|
"Please email us directly (hello@ppl.family) so we can adjust our form.";
|
||||||
|
} else if(!/.+@.+\..+/.test(data.address)) {
|
||||||
|
errors.email = "Please enter a valid email address.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.comment && data.comment.length > 102400) {
|
||||||
|
errors.name = "Name is too long. <br>Please use a shorter version of your name.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Object.keys(errors).length) {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableForm(form) {
|
||||||
|
var elements = form.elements;
|
||||||
|
|
||||||
|
for(var i = 0; i < elements.length; ++i) {
|
||||||
|
elements[i].removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableForm(form) {
|
||||||
|
var elements = form.elements;
|
||||||
|
|
||||||
|
for(var i = 0; i < elements.length; ++i) {
|
||||||
|
elements[i].setAttribute("disabled", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableEmailForms() {
|
||||||
|
enableForm(document.querySelector(".js-inline-email-form"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableEmailForms() {
|
||||||
|
disableForm(document.querySelector(".js-inline-email-form"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function displaySuccess(form) {
|
||||||
|
var successEle = form.querySelector(".success-message");
|
||||||
|
if(successEle) {
|
||||||
|
successEle.classList.remove("js-inactive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideSuccess(form){
|
||||||
|
var successEle = form.querySelector(".success-message");
|
||||||
|
if(successEle) {
|
||||||
|
successEle.classList.add("js-inactive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayErrors(form, errors) {
|
||||||
|
errors = errors || {};
|
||||||
|
|
||||||
|
form.querySelectorAll(".input-error").forEach(function(ele) {
|
||||||
|
ele.classList.add("js-inactive");
|
||||||
|
});
|
||||||
|
form.querySelector(".form-error").classList.add("js-inactive");
|
||||||
|
|
||||||
|
Object.keys(errors).forEach(function(key) {
|
||||||
|
var errorEle;
|
||||||
|
if(key === "_form" && errors[key]) {
|
||||||
|
errorEle = form.querySelector(".form-error");
|
||||||
|
} else if(errors[key]) {
|
||||||
|
var query = "." + key + ".input-error";
|
||||||
|
errorEle = form.querySelector(query);
|
||||||
|
}
|
||||||
|
if(!errorEle) return;
|
||||||
|
|
||||||
|
errorEle.innerHTML = errors[key];
|
||||||
|
errorEle.classList.remove("js-inactive");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitFormData(form) {
|
||||||
|
hideSuccess(form);
|
||||||
|
var data = new FormData(form);
|
||||||
|
|
||||||
|
var msg = {
|
||||||
|
address: data.get("email")
|
||||||
|
, comment: 'telebit: ' + (data.get("name") || '')
|
||||||
|
};
|
||||||
|
|
||||||
|
var errors = validateFormData(msg);
|
||||||
|
displayErrors(form, errors);
|
||||||
|
if(errors) {
|
||||||
|
console.warn("Form validation failed: ", errors);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
disableEmailForms();
|
||||||
|
|
||||||
|
return window.fetch('https://api.ppl.family/api/ppl.family/public/list', {
|
||||||
|
method: 'POST'
|
||||||
|
, cors: true
|
||||||
|
, headers: new Headers({ 'Content-Type': 'application/json' })
|
||||||
|
, body: JSON.stringify(msg)
|
||||||
|
}).then(function (resp) {
|
||||||
|
return resp.json();
|
||||||
|
}).then(function (data) {
|
||||||
|
enableEmailForms();
|
||||||
|
if (data.error) {
|
||||||
|
console.error("Error submitting form: ", data.error);
|
||||||
|
err = {
|
||||||
|
"_form": "Couldn't save email. <br>Try again or email hello@ppl.family directly."
|
||||||
|
};
|
||||||
|
return displayErrors(form, errors);
|
||||||
|
}
|
||||||
|
displaySuccess(form);
|
||||||
|
console.log("Successfully subscribed!");
|
||||||
|
|
||||||
|
form.reset();
|
||||||
|
|
||||||
|
}, function (err) {
|
||||||
|
enableEmailForms();
|
||||||
|
console.error("Error sending form data to server: ", err);
|
||||||
|
displayErrors(form, {
|
||||||
|
"_form": "Unable to send the info to the server.<br>" +
|
||||||
|
"Please try again or email hello@ppl.family directly."
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.body.addEventListener('submit', function (ev) {
|
||||||
|
console.log("Caught event!");
|
||||||
|
function eleMatchesString(ele, selector) {
|
||||||
|
return ele.matches ? ele.matches(selector) : ele.msMatchesSelector(selector);
|
||||||
|
}
|
||||||
|
var form = ev.target;
|
||||||
|
if (!eleMatchesString(form, '.js-inline-email-form')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
submitFormData(form);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
})();
|
|
@ -0,0 +1,47 @@
|
||||||
|
.quickstart-step-text {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-terminal {
|
||||||
|
flex: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.quickstart-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
|
||||||
|
.donate-section p {
|
||||||
|
margin: 1.77777778em 10%;
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-terminal {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: .95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step-name {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,433 @@
|
||||||
|
body{
|
||||||
|
font-family: Source Sans Pro, sans-serrif;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.3333;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, u:hover {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form ul, footer ul, header ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 788px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.77778em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .logo, header .logo {
|
||||||
|
font-size: 1.55556em;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .navigation-menu {
|
||||||
|
display: flex;
|
||||||
|
margin: initial;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .navigation-menu li {
|
||||||
|
margin-left: 1.77778em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero .container {
|
||||||
|
padding-top: 0.44444em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.22222em;
|
||||||
|
margin: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spiel {
|
||||||
|
max-width: 60%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
border: solid 1px white;
|
||||||
|
padding: 0.444444em 0.8888889em;
|
||||||
|
border-radius: 0.1111111em;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-download {
|
||||||
|
margin: 1.33333333em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.link-button.wide {
|
||||||
|
padding: 0.44444em 1.7777776em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-container {
|
||||||
|
margin-top: 1em;
|
||||||
|
position: relative;
|
||||||
|
height: 236px;
|
||||||
|
width: 644px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 544px;
|
||||||
|
height: 236px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-buttons > div {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border: solid 1px #a6a6a6;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-header {
|
||||||
|
background-color: #ededed;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-buttons {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-address-bar {
|
||||||
|
color: #1a1a1a;
|
||||||
|
border: solid 1px #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 20px;
|
||||||
|
font-size: 0.83333em;
|
||||||
|
margin-right: 56px;
|
||||||
|
margin: 8px 56px 8px 20px;
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-address-bar img {
|
||||||
|
height: 17px;
|
||||||
|
float: left;
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-browser-body {
|
||||||
|
font-size: 32px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #bebebe;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-terminal {
|
||||||
|
/*width: 418px;*/
|
||||||
|
width: 512px;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 15px;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #1a1a1a;
|
||||||
|
padding: 24px 24px 20px;
|
||||||
|
bottom: 0;
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1.35;
|
||||||
|
-webkit-box-shadow: -5px 5px 34px 7px rgba(17,17,17,0.18);
|
||||||
|
-moz-box-shadow: -5px 5px 34px 7px rgba(17,17,17,0.18);
|
||||||
|
box-shadow: -5px 5px 26px 10px rgba(17, 17, 17, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-terminal-line:before {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
.demo-terminal-input:before {
|
||||||
|
content: "$";
|
||||||
|
}
|
||||||
|
.demo-terminal-output {
|
||||||
|
padding-left: 1em;
|
||||||
|
text-indent: -1em;
|
||||||
|
}
|
||||||
|
.demo-terminal-output:before {
|
||||||
|
content: ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {text-align: center;font-size: 1.77778em;margin: 0 0 1.25em 0;}
|
||||||
|
|
||||||
|
body {}
|
||||||
|
|
||||||
|
.donate-section {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
padding: 1.777778em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-it {
|
||||||
|
text-align: left;
|
||||||
|
text-indent: 4.3em;
|
||||||
|
margin: 1.75em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent-color {
|
||||||
|
color: rgb(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step-number {
|
||||||
|
border-radius: 1em;
|
||||||
|
height: 1.583333333em;
|
||||||
|
width: 1.5833333333em;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step {
|
||||||
|
font-size: 1.33333em;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step-text {
|
||||||
|
min-width: 9.583336em;
|
||||||
|
margin-right: 1.3333333em;
|
||||||
|
flex: 1 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-terminal {
|
||||||
|
flex: 0 0 36.7em;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8em;
|
||||||
|
width: 36.7em;
|
||||||
|
line-height: 1.33;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.8em 1em 0.8em 2em;
|
||||||
|
}
|
||||||
|
.quickstart-line:before {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
.quickstart-input:before {
|
||||||
|
content: "$ ";
|
||||||
|
}
|
||||||
|
.quickstart-output:before {
|
||||||
|
content: "> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-badges {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-badge {
|
||||||
|
width: 9.9444444em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-badge img, .install-badge svg {
|
||||||
|
width: 1.3333333em;
|
||||||
|
margin: 0.888888889em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature.badge {
|
||||||
|
width: 9.888888889em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-badge img {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
width: 1.333333333em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-badge {
|
||||||
|
width: 9.8888889em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-badge div {
|
||||||
|
margin-top: 0.555555556em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-badges {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donate-section p {
|
||||||
|
margin: 1.7777778em 7.555555556em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
margin: 4em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donate-section h2 {
|
||||||
|
margin: 0 0 0.88888889em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donate-section a.link-button {
|
||||||
|
border: none;
|
||||||
|
width: 11.1111111em;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donate-section .container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.44444444em;
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
border: solid 1px #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form .link-button {
|
||||||
|
border: none;
|
||||||
|
margin-left: 0.88889em;
|
||||||
|
width: 9em;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form {
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
padding: 1.77777778em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form li img {
|
||||||
|
width: 1.111111111em;
|
||||||
|
margin-right: 0.4444444em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #b3b3b3;
|
||||||
|
color: white;
|
||||||
|
padding: 1.444444444em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer li {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 2.2222em;
|
||||||
|
font-size: 0.833333333em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-inactive {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
s {}
|
||||||
|
|
||||||
|
.mailing-list-form ul {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form .container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailing-list-form li {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {}
|
||||||
|
|
||||||
|
.quickstart-terminal.qickstart-terminal-prompt:before {
|
||||||
|
content: "$ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-badge:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-badge:hover path {
|
||||||
|
fill: #ababab;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"] {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-container {
|
||||||
|
max-width: 1025px;
|
||||||
|
width: auto;
|
||||||
|
padding: 0px 3.111111111em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickstart-step-name {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
interval: time between spans appearing
|
||||||
|
transitionTime: the time it takes for the span to finish "sliding" in to place.
|
||||||
|
transitionTime should be <= interval/2;
|
||||||
|
n: total number of spans sliding in and out
|
||||||
|
n should be > 1
|
||||||
|
nth: the value in "nth-child())"
|
||||||
|
*/
|
||||||
|
.sliding-vertical{
|
||||||
|
display: inline;
|
||||||
|
text-indent: 8px;
|
||||||
|
}
|
||||||
|
.sliding-vertical span{
|
||||||
|
animation: topToBottom 15s linear infinite 0s;/* interval * n */
|
||||||
|
-ms-animation: topToBottom 15s linear infinite 0s;/* interval * n */
|
||||||
|
-webkit-animation: topToBottom 15s linear infinite 0s;/* interval * n */
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.sliding-vertical span:nth-child(2){
|
||||||
|
animation-delay: 2.5s;/* (nth - 1) * interval */
|
||||||
|
-ms-animation-delay: 2.5s;/* (nth - 1) * interval */
|
||||||
|
-webkit-animation-delay: 2.5s;/* (nth - 1) * interval */
|
||||||
|
}
|
||||||
|
.sliding-vertical span:nth-child(3){
|
||||||
|
animation-delay: 5s;
|
||||||
|
-ms-animation-delay: 5s;
|
||||||
|
-webkit-animation-delay: 5s;
|
||||||
|
}
|
||||||
|
.sliding-vertical span:nth-child(4){
|
||||||
|
animation-delay: 7.5s;
|
||||||
|
-ms-animation-delay: 7.5s;
|
||||||
|
-webkit-animation-delay: 7.5s;
|
||||||
|
}
|
||||||
|
.sliding-vertical span:nth-child(5){
|
||||||
|
animation-delay: 10s;
|
||||||
|
-ms-animation-delay: 10s;
|
||||||
|
-webkit-animation-delay: 10s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliding-vertical span:nth-child(6){
|
||||||
|
animation-delay: 12.5s;
|
||||||
|
-ms-animation-delay: 12.5s;
|
||||||
|
-webkit-animation-delay: 12.5s;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.sliding-vertical span:nth-child(7){
|
||||||
|
animation-delay: 15s;
|
||||||
|
-ms-animation-delay: 15s;
|
||||||
|
-webkit-animation-delay: 15s;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*topToBottom Animation*/
|
||||||
|
@keyframes topToBottom{
|
||||||
|
0% { opacity: 0; transform: translateY(-50px); }
|
||||||
|
6.667% { opacity: 1; transform: translateY(0px); }/* transitionTime/(interval*n) */
|
||||||
|
16.667% { opacity: 1; transform: translateY(0px); } /* 1/n */
|
||||||
|
23.333% { opacity: 0; transform: translateY(50px); } /* (interval + transitionTime)/(n*interval) */
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes topToBottom{
|
||||||
|
0% { opacity: 0; -moz-transform: translateY(-50px); }
|
||||||
|
6.667% { opacity: 1; -moz-transform: translateY(0px); }
|
||||||
|
16.667% { opacity: 1; -moz-transform: translateY(0px); }
|
||||||
|
23.333% { opacity: 0; -moz-transform: translateY(50px); }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes topToBottom{
|
||||||
|
0% { opacity: 0; -webkit-transform: translateY(-50px); }
|
||||||
|
6.667% { opacity: 1; -webkit-transform: translateY(0px); }
|
||||||
|
16.667% { opacity: 1; -webkit-transform: translateY(0px); }
|
||||||
|
23.333% { opacity: 0; -webkit-transform: translateY(50px); }
|
||||||
|
}
|
||||||
|
@-ms-keyframes topToBottom{
|
||||||
|
0% { opacity: 0; -ms-transform: translateY(-50px); }
|
||||||
|
6.667% { opacity: 1; -ms-transform: translateY(0px); }
|
||||||
|
16.667% { opacity: 1; -ms-transform: translateY(0px); }
|
||||||
|
23.333% { opacity: 0; -ms-transform: translateY(50px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.install-for {
|
||||||
|
margin-top: 3.1111111113em;
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;url=https://docs.google.com/presentation/d/e/2PACX-1vRQ1YyZcTDKYINLvUe8OdaDn_mIoCc0v8XSK-rgI3-b8EldgqpwbZEGmPn7J9pN1vnEJ1-pOcl_T-QP/pub">
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0%;
|
||||||
|
padding: 0%;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
background-image: url("http://www.tshirtvortex.net/wp-content/uploads/thenamesrex.jpg");
|
||||||
|
height: 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg">
|
||||||
|
<center>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h1><a href="https://docs.google.com/presentation/d/e/2PACX-1vRQ1YyZcTDKYINLvUe8OdaDn_mIoCc0v8XSK-rgI3-b8EldgqpwbZEGmPn7J9pN1vnEJ1-pOcl_T-QP/pub?start=false&loop=false&delayms=3000">Access Ability (look ma, no cloud) [slides]</a></h1>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var basedir = path.join(__dirname, 'emails');
|
||||||
|
var files = fs.readdirSync(basedir)
|
||||||
|
|
||||||
|
var emails = {};
|
||||||
|
files.forEach(function (fname) {
|
||||||
|
var fpath = path.join(basedir, fname);
|
||||||
|
var data;
|
||||||
|
var email;
|
||||||
|
var iat;
|
||||||
|
var mdata;
|
||||||
|
if (!/\.data$/.test(fname)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = JSON.parse(fs.readFileSync(fpath));
|
||||||
|
email = fname.replace('\.' + data.domains.join('') + '\.data', '');
|
||||||
|
mdata = JSON.parse(fs.readFileSync(path.join(basedir, email)));
|
||||||
|
if (data.iat) {
|
||||||
|
iat = new Date(data.iat * 1000).toISOString();
|
||||||
|
}
|
||||||
|
if (!emails[email]) {
|
||||||
|
emails[email] = {
|
||||||
|
domains: []
|
||||||
|
, ports: []
|
||||||
|
, nodes: [ { createdAt: iat, scheme: 'mailto', type: 'email', name: email } ]
|
||||||
|
, jtis: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
emails[email].jtis.push(data.id);
|
||||||
|
data.domains.forEach(function (d) {
|
||||||
|
emails[email].domains.push({ createdAt: iat, name: d, wildcard: true, hostname: mdata.hostname
|
||||||
|
, os: mdata.os_type, arch: mdata.os_arch });
|
||||||
|
});
|
||||||
|
data.ports.forEach(function (p) {
|
||||||
|
emails[email].ports.push({ createdAt: iat, number: p, hostname: mdata.hostname
|
||||||
|
, os: mdata.os_type, arch: mdata.os_arch });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
console.log('[\n' + Object.keys(emails).map(function (k) { return JSON.stringify(emails[k]); }).join(',\n') + '\n]');
|
||||||
|
console.log('');
|
||||||
|
console.log('');
|
||||||
|
console.log(Object.keys(emails).join(', '));
|
||||||
|
console.log('');
|
|
@ -0,0 +1,315 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA;
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
} catch(e) {
|
||||||
|
PromiseA = global.Promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
var sfs = require('safe-replace');
|
||||||
|
|
||||||
|
var DB = module.exports = {};
|
||||||
|
DB._savefile = path.join(__dirname, 'permissions.json');
|
||||||
|
DB._load = function () {
|
||||||
|
try {
|
||||||
|
DB._perms = require(DB._savefile);
|
||||||
|
} catch(e) {
|
||||||
|
try {
|
||||||
|
DB._perms = require(DB._savefile + '.bak');
|
||||||
|
} catch(e) {
|
||||||
|
DB._perms = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DB._byDomain = {};
|
||||||
|
DB._byPort = {};
|
||||||
|
DB._byEmail = {};
|
||||||
|
DB._byPpid = {};
|
||||||
|
DB._byId = {};
|
||||||
|
DB._grants = {};
|
||||||
|
DB._grantsMap = {};
|
||||||
|
DB._authz = {};
|
||||||
|
DB._perms.forEach(function (acc) {
|
||||||
|
if ('authz' === acc.type) {
|
||||||
|
DB._authz[acc.id] = acc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (acc.id) {
|
||||||
|
// if account has an id
|
||||||
|
DB._byId[acc.id] = acc;
|
||||||
|
if (!DB._grants[acc.id]) {
|
||||||
|
DB._grantsMap[acc.id] = {};
|
||||||
|
DB._grants[acc.id] = [];
|
||||||
|
}
|
||||||
|
acc.domains.forEach(function (d) {
|
||||||
|
DB._grants[d.name + '|id|' + acc.id] = true;
|
||||||
|
if (!DB._grantsMap[acc.id][d.name]) {
|
||||||
|
DB._grantsMap[acc.id][d.name] = d;
|
||||||
|
DB._grants[acc.id].push(d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (p) {
|
||||||
|
DB._grants[p.number + '|id|' + acc.id] = true;
|
||||||
|
if (!DB._grantsMap[acc.id][p.number]) {
|
||||||
|
DB._grantsMap[acc.id][p.number] = p;
|
||||||
|
DB._grants[acc.id].push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (acc.nodes[0] && 'email' === acc.nodes[0].type) {
|
||||||
|
// if primary (first) node is email
|
||||||
|
//console.log("XXXX email", acc.nodes[0].name);
|
||||||
|
if (!DB._byEmail[acc.nodes[0].name]) {
|
||||||
|
DB._byEmail[acc.nodes[0].name] = {
|
||||||
|
account: acc
|
||||||
|
, node: acc.nodes[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// map domains to all nodes that have permission
|
||||||
|
// (which permission could be granted by more than one account)
|
||||||
|
acc.nodes.forEach(function (node) {
|
||||||
|
if ('mailto' === node.scheme || 'email' === node.type) {
|
||||||
|
if (!DB._grants[node.name]) {
|
||||||
|
DB._grantsMap[node.name] = {};
|
||||||
|
DB._grants[node.name] = [];
|
||||||
|
}
|
||||||
|
acc.domains.forEach(function (d) {
|
||||||
|
DB._grants[d.name + '|' + (node.scheme||node.type) + '|' + node.name] = true;
|
||||||
|
if (!DB._grantsMap[node.name][d.name]) {
|
||||||
|
DB._grantsMap[node.name][d.name] = d;
|
||||||
|
DB._grants[node.name].push(d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (p) {
|
||||||
|
DB._grants[p.number + '|' + (node.scheme||node.type) + '|' + node.name] = true;
|
||||||
|
if (!DB._grantsMap[node.name][p.number]) {
|
||||||
|
DB._grantsMap[node.name][p.number] = p;
|
||||||
|
DB._grants[node.name].push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// TODO this also should be maps/arrays (... or just normal database)
|
||||||
|
acc.domains.forEach(function (domain) {
|
||||||
|
if (DB._byDomain[domain.name]) {
|
||||||
|
console.warn("duplicate domain '" + domain.name + "'");
|
||||||
|
console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
console.warn("::new account '" + DB._byDomain[domain.name].account.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
}
|
||||||
|
DB._byDomain[domain.name] = {
|
||||||
|
account: acc
|
||||||
|
, domain: domain
|
||||||
|
};
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (port) {
|
||||||
|
if (DB._byPort[port.number]) {
|
||||||
|
console.warn("duplicate port '" + port.number + "'");
|
||||||
|
console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
console.warn("::new account '" + DB._byPort[port.number].account.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
}
|
||||||
|
DB._byPort[port.number] = {
|
||||||
|
account: acc
|
||||||
|
, port: port
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB._load();
|
||||||
|
DB.accounts = {};
|
||||||
|
DB.accounts.get = function (obj) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
//console.log('XXXX obj.name', DB._byEmail[obj.name]);
|
||||||
|
return DB._byId[obj.name] || (DB._byEmail[obj.name] || {}).account || null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.accounts.add = function (obj) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
if (obj.id) {
|
||||||
|
// TODO more checks
|
||||||
|
DB._perms.push(obj);
|
||||||
|
} else if ('email' === obj.nodes[0].type || obj.email) {
|
||||||
|
obj.email = undefined;
|
||||||
|
DB._perms.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.authorizations = {};
|
||||||
|
DB.authorizations.create = function (acc, claim) {
|
||||||
|
if (!acc.id || !claim.type || !claim.value) { throw new Error("requires account id"); }
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var authz = DB._authz[acc.id];
|
||||||
|
if (!authz) {
|
||||||
|
authz = {
|
||||||
|
id: acc.id
|
||||||
|
, type: 'authz'
|
||||||
|
, claims: []
|
||||||
|
};
|
||||||
|
DB._authz[acc.id] = authz;
|
||||||
|
DB._perms.push(authz);
|
||||||
|
}
|
||||||
|
// TODO check for unique type:value pairing in claims
|
||||||
|
claim.challenge = crypto.randomBytes(16).toString('hex');
|
||||||
|
claim.createdAt = Date.now();
|
||||||
|
claim.verifiedAt = 0;
|
||||||
|
authz.claims.push(claim);
|
||||||
|
DB.save();
|
||||||
|
return JSON.parse(JSON.stringify(claim));
|
||||||
|
};
|
||||||
|
DB.authorizations.check = function (acc, claim) {
|
||||||
|
var authz = DB._authz[acc.id];
|
||||||
|
var vclaim = null;
|
||||||
|
if (!authz) {
|
||||||
|
return vclaim;
|
||||||
|
}
|
||||||
|
|
||||||
|
authz.claims.some(function (c) {
|
||||||
|
console.log('authz.check', c);
|
||||||
|
if (claim.challenge) {
|
||||||
|
if (c.challenge === claim.challenge) {
|
||||||
|
vclaim = JSON.parse(JSON.stringify(c));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (claim.value === c.value) {
|
||||||
|
vclaim = JSON.parse(JSON.stringify(c));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vclaim;
|
||||||
|
};
|
||||||
|
DB.authorizations.checkAll = function (acc) {
|
||||||
|
var authz = DB._authz[acc.id];
|
||||||
|
if (!authz) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return authz.claims.map(function (claim) {
|
||||||
|
return JSON.parse(JSON.stringify(claim));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.authorizations.verify = function (acc, claim) {
|
||||||
|
var scmp = require('scmp');
|
||||||
|
var authz = DB._authz[acc.id];
|
||||||
|
var vclaim;
|
||||||
|
if (!authz) { return false; }
|
||||||
|
|
||||||
|
authz.claims.some(function (c) {
|
||||||
|
if (scmp(c.challenge, claim.challenge)) {
|
||||||
|
vclaim = c;
|
||||||
|
c.verifiedAt = Date.now();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (vclaim) {
|
||||||
|
DB.save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
DB.domains = {};
|
||||||
|
DB.domains.available = function (name) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
return !DB._byDomain[name];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.domains._add = function (acc, opts) {
|
||||||
|
// TODO verifications to change ownership of a domain
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
var err;
|
||||||
|
//var acc = DB._byId[aid];
|
||||||
|
var domain = {
|
||||||
|
name: (opts.domain || opts.name)
|
||||||
|
, hostname: opts.hostname
|
||||||
|
, os: opts.os
|
||||||
|
, createdAt: new Date().toISOString()
|
||||||
|
, wildcard: opts.wildcard
|
||||||
|
};
|
||||||
|
var pdomain;
|
||||||
|
var parts = (opts.domain || domain.name).split('.').map(function (el, i, arr) {
|
||||||
|
return arr.slice(i).join('.');
|
||||||
|
}).reverse();
|
||||||
|
parts.shift();
|
||||||
|
parts.pop();
|
||||||
|
if (parts.some(function (part) {
|
||||||
|
if (DB._byDomain[part] && DB._byDomain[part].wildcard) {
|
||||||
|
pdomain = part;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
err = new Error("'" + domain.name + "' exists as '" + pdomain + "' and therefore requires an admin to review and approve");
|
||||||
|
err.code = "E_REQ_ADMIN";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (DB._byDomain[domain.name]) {
|
||||||
|
if (acc !== DB._byDomain[domain.name].account) {
|
||||||
|
throw new Error("domain '" + domain.name + "' exists");
|
||||||
|
}
|
||||||
|
// happily ignore non-change
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DB._byDomain[domain.name] = {
|
||||||
|
account: acc
|
||||||
|
, domain: domain
|
||||||
|
};
|
||||||
|
acc.domains.push(domain);
|
||||||
|
DB.save();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.ports = {};
|
||||||
|
DB.ports.available = function (number) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
return !DB._byPort[number];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.ports._add = function (acc, opts) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
//var acc = DB._byId[aid];
|
||||||
|
var port = {
|
||||||
|
number: opts.port || opts.number
|
||||||
|
, hostname: opts.hostname
|
||||||
|
, os: opts.os
|
||||||
|
, createdAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
if (DB._byPort[port.number]) {
|
||||||
|
// TODO verifications
|
||||||
|
throw new Error("port '" + port.number + "' exists");
|
||||||
|
}
|
||||||
|
DB._byPort[port.number] = {
|
||||||
|
account: acc
|
||||||
|
, port: port
|
||||||
|
};
|
||||||
|
acc.ports.push(port);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB._save = function () {
|
||||||
|
return sfs.writeFileAsync(DB._savefile, JSON.stringify(DB._perms));
|
||||||
|
};
|
||||||
|
DB._saveToken = null;
|
||||||
|
DB._savePromises = [];
|
||||||
|
DB._savePromise = PromiseA.resolve();
|
||||||
|
DB.save = function () {
|
||||||
|
clearTimeout(DB._saveToken);
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
function doSave() {
|
||||||
|
DB._savePromise = DB._savePromise.then(function () {
|
||||||
|
return DB._save().then(function (yep) {
|
||||||
|
DB._savePromises.forEach(function (p) {
|
||||||
|
p.resolve(yep);
|
||||||
|
});
|
||||||
|
DB._savePromises.length = 1;
|
||||||
|
}, function (err) {
|
||||||
|
DB._savePromises.forEach(function (p) {
|
||||||
|
p.reject(err);
|
||||||
|
});
|
||||||
|
DB._savePromises.length = 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return DB._savePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB._saveToken = setTimeout(doSave, 2500);
|
||||||
|
DB._savePromises.push({ resolve: resolve, reject: reject });
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "telebit.commercial",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Commercial node.js APIs for Telebit",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"dependencies": {
|
||||||
|
"jwk-to-pem": "^2.0.0",
|
||||||
|
"oauth3.js": "^1.2.5",
|
||||||
|
"scmp": "^1.0.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var perms = require('./permissions.json');
|
||||||
|
var emails = {};
|
||||||
|
perms.forEach(function (p) {
|
||||||
|
p.nodes.forEach(function (n) {
|
||||||
|
if ('email' === n.type) {
|
||||||
|
emails[n.name] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(Object.keys(emails).join(', '));
|
|
@ -10,7 +10,7 @@ function noSniCallback(tag) {
|
||||||
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
|
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
cb(new Error(err));
|
cb(new Error(err));
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.create = function (state) {
|
module.exports.create = function (state) {
|
||||||
|
@ -72,44 +72,38 @@ module.exports.create = function (state) {
|
||||||
state.tlsInvalidSniServer.on('tlsClientError', function () {
|
state.tlsInvalidSniServer.on('tlsClientError', function () {
|
||||||
console.error('tlsClientError InvalidSniServer');
|
console.error('tlsClientError InvalidSniServer');
|
||||||
});
|
});
|
||||||
state.createHttpInvalid = function (opts) {
|
state.httpsInvalid = function (servername, socket) {
|
||||||
return http.createServer(function (req, res) {
|
|
||||||
if (!opts.servername) {
|
|
||||||
res.statusCode = 422;
|
|
||||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
||||||
res.end(
|
|
||||||
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
|
||||||
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
|
||||||
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
|
||||||
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO use req.headers.host instead of servername (since domain fronting is disabled anyway)
|
|
||||||
res.statusCode = 502;
|
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
||||||
res.end(
|
|
||||||
"<h1>Oops!</h1>"
|
|
||||||
+ "<p>It looks like '" + encodeURIComponent(opts.servername) + "' isn't connected right now.</p>"
|
|
||||||
+ "<p><small>Last seen: " + opts.ago + "</small></p>"
|
|
||||||
+ "<p><small>Error: 502 Bad Gateway</small></p>"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
state.httpsInvalid = function (opts, socket) {
|
|
||||||
// none of these methods work:
|
// none of these methods work:
|
||||||
// httpsServer.emit('connection', socket); // this didn't work
|
// httpsServer.emit('connection', socket); // this didn't work
|
||||||
// tlsServer.emit('connection', socket); // this didn't work either
|
// tlsServer.emit('connection', socket); // this didn't work either
|
||||||
//console.log('chunkLen', firstChunk.byteLength);
|
//console.log('chunkLen', firstChunk.byteLength);
|
||||||
|
|
||||||
console.log('[httpsInvalid] servername', opts.servername);
|
console.log('[httpsInvalid] servername', servername);
|
||||||
//state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
|
//state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
|
||||||
var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) {
|
var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) {
|
||||||
console.log('[tlsInvalid] tls connection');
|
console.log('[tlsInvalid] tls connection');
|
||||||
// We create an entire http server object because it's difficult to figure out
|
// things get a little messed up here
|
||||||
// how to access the original tlsSocket to get the servername
|
var httpInvalidSniServer = http.createServer(function (req, res) {
|
||||||
state.createHttpInvalid(opts).emit('connection', tlsSocket);
|
if (!servername) {
|
||||||
|
res.statusCode = 422;
|
||||||
|
res.end(
|
||||||
|
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
||||||
|
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
||||||
|
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
||||||
|
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end(
|
||||||
|
"You came in hot looking for '" + servername + "' and, granted, the IP address for that domain"
|
||||||
|
+ " must be pointing here (or else how could you be here?), nevertheless either it's not registered"
|
||||||
|
+ " in the internal system at all (which Seth says isn't even a thing) or there is no device"
|
||||||
|
+ " connected on the south side of the network which has informed me that it's ready to have traffic"
|
||||||
|
+ " for that domain forwarded to it (sorry I didn't check that deeply to determine which).\n\n"
|
||||||
|
+ "Either way, you're doing strange things that make me feel uncomfortable... Please don't touch me there any more.");
|
||||||
|
});
|
||||||
|
httpInvalidSniServer.emit('connection', tlsSocket);
|
||||||
});
|
});
|
||||||
tlsInvalidSniServer.on('tlsClientError', function () {
|
tlsInvalidSniServer.on('tlsClientError', function () {
|
||||||
console.error('tlsClientError InvalidSniServer httpsInvalid');
|
console.error('tlsClientError InvalidSniServer httpsInvalid');
|
||||||
|
|
|
@ -68,6 +68,7 @@ module.exports.create = function (state) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (initToken) {
|
if (initToken) {
|
||||||
|
console.log('[wss.onConnection] token provided in http headers');
|
||||||
return Server.addToken(state, srv, initToken).then(function () {
|
return Server.addToken(state, srv, initToken).then(function () {
|
||||||
Server.init(state, srv);
|
Server.init(state, srv);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
|
|
@ -172,7 +172,6 @@ var Server = {
|
||||||
, _initSocketHandlers: function (state, srv) {
|
, _initSocketHandlers: function (state, srv) {
|
||||||
function refreshTimeout() {
|
function refreshTimeout() {
|
||||||
srv.lastActivity = Date.now();
|
srv.lastActivity = Date.now();
|
||||||
Devices.touchDevice(state.deviceLists, srv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkTimeout() {
|
function checkTimeout() {
|
||||||
|
|
|
@ -2,16 +2,6 @@
|
||||||
|
|
||||||
var sni = require('sni');
|
var sni = require('sni');
|
||||||
var pipeWs = require('./pipe-ws.js');
|
var pipeWs = require('./pipe-ws.js');
|
||||||
var ago = require('./ago.js').AGO;
|
|
||||||
var up = Date.now();
|
|
||||||
|
|
||||||
function fromUptime(ms) {
|
|
||||||
if (ms) {
|
|
||||||
return ago(Date.now() - ms);
|
|
||||||
} else {
|
|
||||||
return "Not seen since relay restarted, " + ago(Date.now() - up);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.createTcpConnectionHandler = function (state) {
|
module.exports.createTcpConnectionHandler = function (state) {
|
||||||
var Devices = state.Devices;
|
var Devices = state.Devices;
|
||||||
|
@ -37,16 +27,6 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
var str;
|
var str;
|
||||||
var m;
|
var m;
|
||||||
|
|
||||||
if (!firstChunk) {
|
|
||||||
try {
|
|
||||||
conn.end();
|
|
||||||
} catch(e) {
|
|
||||||
console.error("[lib/unwrap-tls.js] Error:", e);
|
|
||||||
conn.destroy();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//conn.pause();
|
//conn.pause();
|
||||||
conn.unshift(firstChunk);
|
conn.unshift(firstChunk);
|
||||||
|
|
||||||
|
@ -58,15 +38,8 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
|
|
||||||
// defer after return (instead of being in many places)
|
// defer after return (instead of being in many places)
|
||||||
function deferData(fn) {
|
function deferData(fn) {
|
||||||
if ('httpsInvalid' === fn) {
|
if (fn) {
|
||||||
state[fn]({
|
|
||||||
servername: servername
|
|
||||||
, ago: fromUptime(Devices.lastSeen(state.deviceLists, servername))
|
|
||||||
}, conn);
|
|
||||||
} else if (fn) {
|
|
||||||
state[fn](servername, conn);
|
state[fn](servername, conn);
|
||||||
} else {
|
|
||||||
console.error("[SANITY ERROR] '" + fn + "' doesn't have a state handler");
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
|
@ -75,81 +48,33 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpOutcomes = {
|
function tryTls() {
|
||||||
missingServername: function () {
|
var vhost;
|
||||||
console.log("[debug] [http] missing servername");
|
|
||||||
// TODO use a more specific error page
|
|
||||||
deferData('handleInsecureHttp');
|
|
||||||
}
|
|
||||||
, requiresSetup: function () {
|
|
||||||
console.log("[debug] [http] requires setup");
|
|
||||||
// TODO Insecure connections for setup will not work on secure domains (i.e. .app)
|
|
||||||
state.httpSetupServer.emit('connection', conn);
|
|
||||||
}
|
|
||||||
, isInternal: function () {
|
|
||||||
console.log("[debug] [http] is known internally (admin)");
|
|
||||||
if (/well-known/.test(str)) {
|
|
||||||
deferData('handleHttp');
|
|
||||||
} else {
|
|
||||||
deferData('handleInsecureHttp');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, isVhost: function () {
|
|
||||||
console.log("[debug] [http] is vhost (normal server)");
|
|
||||||
if (/well-known/.test(str)) {
|
|
||||||
deferData('handleHttp');
|
|
||||||
} else {
|
|
||||||
deferData('handleInsecureHttp');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, assumeExternal: function () {
|
|
||||||
console.log("[debug] [http] assume external");
|
|
||||||
var service = 'http';
|
|
||||||
|
|
||||||
if (!Devices.exist(state.deviceLists, servername)) {
|
if (!servername) {
|
||||||
// It would be better to just re-read the host header rather
|
|
||||||
// than creating a whole server object, but this is a "rare"
|
|
||||||
// case and I'm feeling lazy right now.
|
|
||||||
console.log("[debug] [http] no device connected");
|
|
||||||
state.createHttpInvalid({
|
|
||||||
servername: servername
|
|
||||||
, ago: fromUptime(Devices.lastSeen(state.deviceLists, servername))
|
|
||||||
}).emit('connection', conn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make https redirect configurable on a per-domain basis
|
|
||||||
// /^\/\.well-known\/acme-challenge\//.test(str)
|
|
||||||
if (/well-known/.test(str)) {
|
|
||||||
// HTTP
|
|
||||||
console.log("[debug] [http] passthru");
|
|
||||||
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
console.log("[debug] [http] redirect to https");
|
|
||||||
deferData('handleInsecureHttp');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var tlsOutcomes = {
|
|
||||||
missingServername: function () {
|
|
||||||
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
|
||||||
deferData('httpsInvalid');
|
deferData('httpsInvalid');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
, requiresSetup: function () {
|
|
||||||
|
if (!state.servernames.length) {
|
||||||
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
||||||
deferData('httpsSetupServer');
|
deferData('httpsSetupServer');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
, isInternal: function () {
|
|
||||||
|
if (-1 !== state.servernames.indexOf(servername)) {
|
||||||
if (state.debug) { console.log("[Admin]", servername); }
|
if (state.debug) { console.log("[Admin]", servername); }
|
||||||
deferData('httpsTunnel');
|
deferData('httpsTunnel');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
, isVhost: function (vhost) {
|
|
||||||
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
|
if (state.config.nowww && /^www\./i.test(servername)) {
|
||||||
deferData('httpsVhost');
|
console.log("TODO: use www bare redirect");
|
||||||
}
|
}
|
||||||
, assumeExternal: function () {
|
|
||||||
var nextDevice = Devices.next(state.deviceLists, servername);
|
function run() {
|
||||||
|
var nextDevice = Devices.next(state.deviceLists, servername);
|
||||||
if (!nextDevice) {
|
if (!nextDevice) {
|
||||||
if (state.debug) { console.log("No devices match the given servername"); }
|
if (state.debug) { console.log("No devices match the given servername"); }
|
||||||
deferData('httpsInvalid');
|
deferData('httpsInvalid');
|
||||||
|
@ -157,33 +82,27 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
|
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
|
||||||
|
deferData();
|
||||||
pipeWs(servername, service, nextDevice, conn, serviceport);
|
pipeWs(servername, service, nextDevice, conn, serviceport);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
function handleConnection(outcomes) {
|
|
||||||
var vhost;
|
|
||||||
|
|
||||||
// No routing information available
|
|
||||||
if (!servername) { outcomes.missingServername(); return; }
|
|
||||||
// Server needs to be set up
|
|
||||||
if (!state.servernames.length) { outcomes.requiresSetup(); return; }
|
|
||||||
// This is one of the admin domains
|
|
||||||
if (-1 !== state.servernames.indexOf(servername)) { outcomes.isInternal(); return; }
|
|
||||||
|
|
||||||
// TODO don't run an fs check if we already know this is working elsewhere
|
// TODO don't run an fs check if we already know this is working elsewhere
|
||||||
//if (!state.validHosts) { state.validHosts = {}; }
|
//if (!state.validHosts) { state.validHosts = {}; }
|
||||||
if (state.config.vhost) {
|
if (state.config.vhost) {
|
||||||
vhost = state.config.vhost.replace(/:hostname/, servername);
|
vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist'));
|
||||||
|
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
|
||||||
|
//state.httpsVhost(servername, conn);
|
||||||
|
//return;
|
||||||
require('fs').readdir(vhost, function (err, nodes) {
|
require('fs').readdir(vhost, function (err, nodes) {
|
||||||
if (state.debug && err) { console.log("VHOST error", err); }
|
if (state.debug && err) { console.log("VHOST error", err); }
|
||||||
if (err || !nodes) { outcomes.assumeExternal(); return; }
|
if (err || !nodes) { run(); return; }
|
||||||
outcomes.isVhost(vhost);
|
//if (nodes) { deferData('httpsVhost'); return; }
|
||||||
|
deferData('httpsVhost');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outcomes.assumeExternal();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
||||||
|
@ -192,19 +111,40 @@ module.exports.createTcpConnectionHandler = function (state) {
|
||||||
service = 'https';
|
service = 'https';
|
||||||
servername = (sni(firstChunk)||'').toLowerCase().trim();
|
servername = (sni(firstChunk)||'').toLowerCase().trim();
|
||||||
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
|
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
|
||||||
handleConnection(tlsOutcomes);
|
tryTls();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
|
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
|
||||||
// (probably) HTTP
|
|
||||||
str = firstChunk.toString();
|
str = firstChunk.toString();
|
||||||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
||||||
servername = (m && m[1].toLowerCase() || '').split(':')[0];
|
servername = (m && m[1].toLowerCase() || '').split(':')[0];
|
||||||
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
|
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
|
||||||
|
|
||||||
if (/HTTP\//i.test(str)) {
|
if (/HTTP\//i.test(str)) {
|
||||||
handleConnection(httpOutcomes);
|
if (!state.servernames.length) {
|
||||||
|
console.info("[tcp] No admin servername. Entering setup mode.");
|
||||||
|
deferData();
|
||||||
|
state.httpSetupServer.emit('connection', conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
service = 'http';
|
||||||
|
// TODO make https redirect configurable
|
||||||
|
// /^\/\.well-known\/acme-challenge\//.test(str)
|
||||||
|
if (/well-known/.test(str)) {
|
||||||
|
// HTTP
|
||||||
|
if (Devices.exist(state.deviceLists, servername)) {
|
||||||
|
deferData();
|
||||||
|
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deferData('handleHttp');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect to https
|
||||||
|
deferData('handleInsecureHttp');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
package.json
|
@ -37,20 +37,32 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js",
|
"homepage": "https://git.coolaj86.com/coolaj86/telebit-relay.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^3.5.1",
|
"@coolaj86/urequest": "^1.3.5",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
"cluster-store": "^2.0.8",
|
"cluster-store": "^2.0.8",
|
||||||
|
"connect-cors": "^0.5.6",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"express": "^4.16.3",
|
||||||
"finalhandler": "^1.1.1",
|
"finalhandler": "^1.1.1",
|
||||||
"greenlock": "^2.2.4",
|
"greenlock": "^2.2.4",
|
||||||
"human-readable-ids": "^1.0.4",
|
"human-readable-ids": "^1.0.4",
|
||||||
"js-yaml": "^3.11.0",
|
"js-yaml": "^3.11.0",
|
||||||
"jsonwebtoken": "^8.3.0",
|
"jsonwebtoken": "^8.3.0",
|
||||||
|
"jwk-to-pem": "^2.0.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"nowww": "^1.2.1",
|
||||||
"proxy-packer": "^2.0.0",
|
"proxy-packer": "^2.0.0",
|
||||||
"recase": "^1.0.4",
|
"recase": "^1.0.4",
|
||||||
"redirect-https": "^1.1.5",
|
"redirect-https": "^1.1.5",
|
||||||
|
"request": "^2.87.0",
|
||||||
|
"safe-replace": "^1.0.3",
|
||||||
"serve-static": "^1.13.2",
|
"serve-static": "^1.13.2",
|
||||||
"sni": "^1.0.0",
|
"sni": "^1.0.0",
|
||||||
"ws": "^5.1.1"
|
"ws": "^5.1.1"
|
||||||
},
|
},
|
||||||
|
"trulyOptionalDependencies": {
|
||||||
|
"bluebird": "^3.5.1"
|
||||||
|
},
|
||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.2.1"
|
"node": "10.2.1"
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
name: telebit-relay
|
|
||||||
version: '0.20.0'
|
|
||||||
summary: Because friends don't let friends localhost
|
|
||||||
description: |
|
|
||||||
A server that works in combination with Telebit Remote
|
|
||||||
to allow you to serve http and https from any computer,
|
|
||||||
anywhere through a secure tunnel.
|
|
||||||
|
|
||||||
grade: stable
|
|
||||||
confinement: strict
|
|
||||||
|
|
||||||
apps:
|
|
||||||
telebit-relay:
|
|
||||||
command: telebit-relay --config $SNAP_COMMON/config.yml
|
|
||||||
plugs: [network, network-bind]
|
|
||||||
daemon: simple
|
|
||||||
|
|
||||||
parts:
|
|
||||||
telebit-relay:
|
|
||||||
plugin: nodejs
|
|
||||||
node-engine: 10.13.0
|
|
||||||
source: .
|
|
||||||
override-build: |
|
|
||||||
snapcraftctl build
|
|