From 07a784f0d654fcd552069e85b314ea7c29cb5dd6 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sat, 6 Jun 2026 22:18:37 +0200 Subject: move Jekyll source files to src/ --- src/assets/js/main.js | 1117 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1117 insertions(+) create mode 100644 src/assets/js/main.js (limited to 'src/assets/js/main.js') diff --git a/src/assets/js/main.js b/src/assets/js/main.js new file mode 100644 index 0000000..ef57764 --- /dev/null +++ b/src/assets/js/main.js @@ -0,0 +1,1117 @@ +var iface = 'wg0'; + +function parse_placeholder(val) { + return val; +} + +function parse_boolean(val) { + if (val === 'true') + return true; + if (val === 'false') + return false; + throw new Error('Cannot parse as a boolean.'); +} + +function parse_boolean_or_string(val) { + if (typeof val == 'boolean') { + return val; + } + return parse_boolean(val); +} + +function try_parse_boolean_or_string(val) { + try { + return parse_boolean_or_string(val); + } catch (err) { + return null; + } +} + +function parse_positive_integer(src) { + var val = parseInt(src); + if (isNaN(val) || val < 1) + throw new Error('Cannot parse as a positive integer.'); + return val; +} + +function parse_keepalive(src) { + return parse_positive_integer(src); +} + +function parse_tunnel_everything(src) { + return parse_boolean_or_string(src); +} + +function parse_endpoint(val) { + val = val.trim(); + if (val.length == 0) + throw new Error('Server endpoint cannot be an empty string.'); + if (!val.match(/^.+:[0-9]+$/)) + throw new Error('Please specify a host and a port in the HOST:PORT format.'); + return val; +} + +function parse_key(val) { + val = val.trim(); + if (val.length == 0) + throw new Error('Key as used by WireGuard cannot be an empty string.'); + if (!val.match(/^[0-9a-zA-Z+/=]+$/)) + throw new Error('Key as used by WireGuard must be Base64-encoded.'); + return val; +} + +var ip_address_lib = require('ip-address'); + +function parse_ip_internal(src, parser) { + src = src.trim(); + if (src.length == 0) + throw new Error('IP address cannot be an empty string.'); + + try { + var result = new parser(src); + } catch (err) { + throw new Error('Sorry, IP address validation failed with the following error:\n' + err.message); + } + return result; +} + +function parse_ip(src, parser) { + var result = parse_ip_internal(src, parser); + + if (result.parsedSubnet) + throw new Error('Please specify address without the netmask.') + + return result.correctForm(); +} + +function parse_ip_cidr(src, parser) { + var result = parse_ip_internal(src, parser); + + if (!result.parsedSubnet) + throw new Error('Please specify address using the CIDR format (including the netmask).'); + + var addr = result.correctForm(); + var netmask = result.subnetMask; + var network_id = result.startAddress().correctForm(); + + return [addr, netmask, network_id]; +} + +function parse_ipv4(src) { + var result = parse_ip(src, ip_address_lib.Address4); + return result; +} + +function parse_ipv4_cidr(src) { + var parser = ip_address_lib.Address4; + var result = parse_ip_cidr(src, ip_address_lib.Address4); + + var addr = result[0]; + var netmask = result[1]; + var network_id = result[2]; + var broadcast_addr = new parser(src).endAddress().correctForm(); + + if (addr == network_id && netmask != 32) + throw new Error('Please use a real host IP address, not a network identifier.'); + if (addr == broadcast_addr && netmask != 32) + throw new Error('Please use a real host IP address, not a broadcast address.'); + + return result; +} + +function parse_ipv6(val) { + return parse_ip(val, ip_address_lib.Address6); +} + +function parse_ipv6_cidr(val) { + return parse_ip_cidr(val, ip_address_lib.Address6); +} + +function parse_ip_list(val, addr_parser) { + var parts = val.split(/, */); + var result = []; + parts.forEach(function(part) { + result.push(addr_parser(part)); + }); + return result; +} + +var Input = function(name) { + this.name = name; + + if (this.is_checkbox()) { + // Keep the checked property and the value consistent. + $(this.input_id()).change(function(evt) { + var elem = evt.target; + elem.value = elem.checked; + }); + } +} + +Input.prototype.input_id = function() { + return '#param_' + this.name; +} + +Input.prototype.error_id = function() { + return this.input_id() + '_error'; +} + +Input.prototype.container_id = function() { + return this.input_id() + '_container'; +} + +Input.prototype.is_checkbox = function() { + return $(this.input_id()).attr('type') == 'checkbox'; +} + +Input.prototype.value = function() { + return $(this.input_id()).val(); +} + +Input.prototype.has_value = function() { + return this.value().length > 0; +} + +Input.prototype.set = function(value) { + if (this.is_checkbox() && try_parse_boolean_or_string(value)) + $(this.input_id()).prop('checked', true); + $(this.input_id()).val(value); +} + +Input.prototype.show = function() { + $(this.container_id()).show(); +} + +Input.prototype.hide = function() { + $(this.container_id()).hide(); +} + +Input.prototype.show_error = function(msg) { + this.show(); + $(this.error_id()).text(msg); + $(this.error_id()).show(); +} + +Input.prototype.hide_error = function() { + $(this.error_id()).hide(); +} + +var Value = function(name, parser) { + this.name = name; + this.input = new Input(name); + this.parser = parser; + this.optional = false; + this.advanced = false; + + this.value = null; + this.error = null; +} + +Value.prototype.parse = function() { + return this.parse_from(this.input.value()); +} + +Value.prototype.parse_from = function(src) { + try { + var value = src; + switch (typeof value) { + case 'boolean': + value = this.parser(value); + break; + default: + if (this.optional && value.length == 0) + break; + value = this.parser(value); + break; + } + this.input.set(src); + this.set_value(value); + return true; + } catch (err) { + this.set_error(err.message); + return false; + } +} + +Value.prototype.set_value = function(value) { + this.value = value; + this.error = null; + this.input.hide_error(); +} + +Value.prototype.set_error = function(msg) { + this.value = null; + this.error = msg; + this.input.show_error(this.error); +} + +Value.prototype.is_set = function() { + return this.input.has_value(); +} + +Value.prototype.show = function() { + this.input.show(); +} + +Value.prototype.hide = function() { + this.input.hide(); +} + +var Endpoint = function(name) { + Value.call(this, name, parse_endpoint); +} + +Endpoint.prototype = Object.create(Value.prototype); +Endpoint.prototype.constructor = Endpoint; + +var Key = function(name) { + Value.call(this, name, parse_key); +} + +Key.prototype = Object.create(Value.prototype); +Key.prototype.constructor = Key; + +var IPAddr = function(name, parser) { + Value.call(this, name, parser); +} + +IPAddr.prototype = Object.create(Value.prototype); +IPAddr.prototype.constructor = IPAddr; + +IPAddr.prototype.set_value = function(value) { + Value.prototype.set_value.call(this, value); + + this.addr = this.value[0]; + this.netmask = this.value[1]; + this.network_id = this.value[2]; +} + +IPAddr.prototype.full_address = function() { + return this.addr + '/' + this.netmask; +} + +IPAddr.prototype.allowed_ips_client = function() { + // As of this writing, _every_ tool accepts something like 192.168.0.1/24 + // here, _except_ for the Android app, which _requires_ the least + // significant bits to be zeroed out. + return this.network_id + '/' + this.netmask; +} + +var IPv4 = function(name) { + IPAddr.call(this, name, parse_ipv4_cidr); +} + +IPv4.prototype = Object.create(IPAddr.prototype); +IPv4.prototype.constructor = IPv4; + +IPv4.prototype.allowed_ips_server = function() { + return this.addr + '/' + 32; +} + +var IPv6 = function(name) { + IPAddr.call(this, name, parse_ipv6_cidr); +} + +IPv6.prototype = Object.create(IPAddr.prototype); +IPv6.prototype.constructor = IPv6; + +IPv6.prototype.allowed_ips_server = function() { + return this.addr + '/' + 128; +} + +var Keepalive = function(name) { + Value.call(this, name, parse_keepalive); + this.optional = true; + this.advanced = true; +} + +Keepalive.prototype = Object.create(Value.prototype); +Keepalive.prototype.constructor = Keepalive; + +var TunnelEverything = function(name) { + Value.call(this, name, parse_tunnel_everything); + this.optional = true; + this.advanced = true; +} + +TunnelEverything.prototype = Object.create(Value.prototype); +TunnelEverything.prototype.constructor = TunnelEverything; + +var IPAddrList = function(name, parser) { + Value.call(this, name, function(val) { + return parse_ip_list(val, parser); + }); +} + +IPAddrList.prototype = Object.create(Value.prototype); +IPAddrList.prototype.constructor = IPAddrList; + +var DNS_IPv4 = function(name) { + IPAddrList.call(this, name, parse_ipv4); + this.optional = true; + this.advanced = true; +} + +DNS_IPv4.prototype = Object.create(IPAddrList.prototype); +DNS_IPv4.prototype.constructor = DNS_IPv4; + +var DNS_IPv6 = function(name) { + IPAddrList.call(this, name, parse_ipv6); + this.optional = true; + this.advanced = true; +} + +DNS_IPv6.prototype = Object.create(IPAddrList.prototype); +DNS_IPv6.prototype.constructor = DNS_IPv6; + +var Data = function() { + this.server_public = new Key('server_public'); + this.server_endpoint = new Endpoint('server_endpoint'); + this.preshared = new Key('preshared'); + this.client_public = new Key('client_public'); + this.client_private = new Key('client_private'); + this.client_ipv4 = new IPv4('client_ipv4'); + this.client_ipv6 = new IPv6('client_ipv6'); + this.keepalive = new Keepalive('keepalive'); + this.tunnel_everything = new TunnelEverything('tunnel_everything'); + this.dns_ipv4 = new DNS_IPv4('dns_ipv4'); + this.dns_ipv6 = new DNS_IPv6('dns_ipv6'); + + this.values = [ + this.server_public, + this.server_endpoint, + this.preshared, + this.client_public, + this.client_private, + this.client_ipv4, + this.client_ipv6, + this.keepalive, + this.tunnel_everything, + this.dns_ipv4, + this.dns_ipv6 + ]; +} + +Data.prototype.set_from_url = function(url) { + var url = new URL(url); + var params = new URLSearchParams(url.search); + var has = false; + + this.values.forEach(function(value) { + if (params.has(value.name)) { + has = true; + value.input.set(params.get(value.name)); + } + }); + + return has; +} + +Data.prototype.set_from_this_url = function() { + return this.set_from_url(window.location.href); +}; + +Data.prototype.get_perma_url = function() { + var url = new URL(window.location.href); + url.search = ''; + var params = new URLSearchParams(); + this.values.forEach(function(value) { + params.append(value.name, value.input.value()); + }); + url.search = params.toString(); + return url.toString(); +} + +Data.prototype.parse = function() { + var success = true; + + this.values.forEach(function(value) { + if (!value.parse()) + success = false; + }); + + if (success) { + if (!$('#advanced_params').prop('checked')) + this.hide_advanced(); + this.hide_error(); + } else { + this.show_error(); + } + return success; +} + +Data.prototype.show_error = function() { + $('#params_error').text('Please correct the input errors above first.'); + $('#params_error').show(); +} + +Data.prototype.hide_error = function() { + $('#params_error').hide(); +} + +Data.prototype.show_advanced = function() { + this.values.forEach(function(value) { + if (!value.advanced) + return; + value.show(); + }); +} + +Data.prototype.hide_advanced = function() { + this.values.forEach(function(value) { + if (!value.advanced) + return; + value.hide(); + }); +} + +function basename(path) { + return path.substring(path.lastIndexOf('/') + 1); +} + +function download(file_name, text) { + // The type must be application/octet-stream; if it's text/plain, the + // Android Chrome appends the .txt suffix to the downloaded file names. + var blob = new Blob([text], {type: 'application/octet-stream'}); + var url = URL.createObjectURL(blob); + + var link = $(''); + link.prop('href', url); + link.prop('download', file_name); + + // Whoever thought of this [0] is fucking crazy: + // https://stackoverflow.com/a/36483380/514684 + // It literally silently does nothing if this is omitted. + link[0].click(); +} + +function copy_to_clipboard(elem) { + // This is rather neat, thanks: + // https://stackoverflow.com/a/22581382/514684 + + // Apparently, you can only do execCommand('copy') from and + //