Formatting and workflow organization changes

This commit is contained in:
00Fjongl 2024-08-08 23:10:58 -05:00
parent 461c89212f
commit 8c9f79d3e7
13 changed files with 1010 additions and 979 deletions

View file

@ -26,7 +26,7 @@ jobs:
run: npm run build run: npm run build
- name: Start server - name: Start server
run: npm run manual-start run: npm run workflow-test
- name: Test server response - name: Test server response
run: npm test run: npm test
@ -53,7 +53,7 @@ jobs:
run: npm run build run: npm run build
- name: Start server - name: Start server
run: npm run manual-start run: npm run workflow-test
- name: Test server response - name: Test server response
run: npm test run: npm test

View file

@ -1 +1,2 @@
/lib /lib
/views/assets/js/*.js

View file

@ -12,7 +12,8 @@
"manual-start": "node run-command.mjs start", "manual-start": "node run-command.mjs start",
"kill": "node run-command.mjs stop kill", "kill": "node run-command.mjs stop kill",
"build": "node run-command.mjs build && cd lib/rammerhead && npm install && npm run build", "build": "node run-command.mjs build && cd lib/rammerhead && npm install && npm run build",
"proxy-validator": "node proxyServiceValidator.js" "proxy-validator": "node proxyServiceValidator.js",
"workflow-test": "node run-command.mjs workflow"
}, },
"keywords": [ "keywords": [
"proxy", "proxy",

View file

@ -1,5 +1,5 @@
// This file is solely used for the automatically run GitHub job, which checks to // This file is solely used for the automatically run GitHub job, which checks to
// see if all HU LTS code is working properly (at least on an Ubuntu machine). // see if all HU LTS code is working properly (at least on an Ubuntu machine).
const axios = require('axios'); const axios = require('axios');
const puppeteer = require('puppeteer'); const puppeteer = require('puppeteer');
@ -122,21 +122,21 @@ const testCommonJSOnPage = async () => {
} }
}); });
// Locate the omnibox element on the Rammerhead page. // Locate the omnibox element on the Rammerhead page.
let omnibox = document.getElementById('pr-rh'); let omnibox = document.getElementById('pr-rh');
omnibox = omnibox && omnibox.querySelector('input[type=text]'); omnibox = omnibox && omnibox.querySelector('input[type=text]');
if (omnibox) { if (omnibox) {
try { try {
// Send an artificial input to the omnibox. The omnibox will create // Send an artificial input to the omnibox. The omnibox will create
// a proxy URL and leave it as the input value in response. // a proxy URL and leave it as the input value in response.
const urlPath = 'example.com'; const urlPath = 'example.com';
omnibox.value = urlPath; omnibox.value = urlPath;
await omnibox.dispatchEvent( await omnibox.dispatchEvent(
new KeyboardEvent('keydown', { code: 'Validator Test' }) new KeyboardEvent('keydown', { code: 'Validator Test' })
); );
// Wait up to 5 seconds for the omnibox to finish updating. // Wait up to 5 seconds for the omnibox to finish updating.
const loadUrl = new Promise((resolve) => { const loadUrl = new Promise((resolve) => {
if (omnibox.value !== urlPath) resolve(omnibox.value); if (omnibox.value !== urlPath) resolve(omnibox.value);
else else
@ -147,7 +147,7 @@ const testCommonJSOnPage = async () => {
timeout = new Promise((resolve) => { timeout = new Promise((resolve) => {
setTimeout(() => resolve(omnibox.value), 40000); setTimeout(() => resolve(omnibox.value), 40000);
}), }),
// Record the proxy URL that the omnibox left here. // Record the proxy URL that the omnibox left here.
rammerheadUrl = await Promise.race([loadUrl, timeout]); rammerheadUrl = await Promise.race([loadUrl, timeout]);
console.log('Generated Rammerhead URL:', rammerheadUrl); console.log('Generated Rammerhead URL:', rammerheadUrl);
results.rammerhead = rammerheadUrl ? rammerheadUrl : 'failure'; results.rammerhead = rammerheadUrl ? rammerheadUrl : 'failure';
@ -225,8 +225,8 @@ xx xx
? resolve() ? resolve()
: window.addEventListener('load', resolve); : window.addEventListener('load', resolve);
// Wait until a service worker is registered before continuing. // Wait until a service worker is registered before continuing.
// Also make sure the document is loaded. // Also make sure the document is loaded.
const waitForWorker = async () => const waitForWorker = async () =>
setTimeout(async () => { setTimeout(async () => {
(await navigator.serviceWorker.getRegistrations()).length >= 1 (await navigator.serviceWorker.getRegistrations()).length >= 1
@ -237,32 +237,32 @@ xx xx
waitForWorker(); waitForWorker();
}); });
// Locate the omnibox element on the Ultraviolet page. // Locate the omnibox element on the Ultraviolet page.
let omnibox = document.getElementById('pr-uv'); let omnibox = document.getElementById('pr-uv');
omnibox = omnibox && omnibox.querySelector('input[type=text]'); omnibox = omnibox && omnibox.querySelector('input[type=text]');
if (omnibox) { if (omnibox) {
// For the hacky URL test, use the URL page's EXACT title. // For the hacky URL test, use the URL page's EXACT title.
const website = { const website = {
path: 'example.com', path: 'example.com',
title: 'Example Domain', title: 'Example Domain',
}; };
try { try {
// Send an artificial input to the omnibox. The omnibox will create // Send an artificial input to the omnibox. The omnibox will create
// a proxy URL and leave it as the input value in response. // a proxy URL and leave it as the input value in response.
omnibox.value = website.path; omnibox.value = website.path;
await omnibox.dispatchEvent( await omnibox.dispatchEvent(
new KeyboardEvent('keydown', { code: 'Validator Test' }) new KeyboardEvent('keydown', { code: 'Validator Test' })
); );
// Record the proxy URL that the omnibox left here. // Record the proxy URL that the omnibox left here.
const generatedUrl = omnibox.value; const generatedUrl = omnibox.value;
console.log('Generated Ultraviolet URL:', generatedUrl); console.log('Generated Ultraviolet URL:', generatedUrl);
results[0].ultraviolet = generatedUrl ? generatedUrl : 'failure'; results[0].ultraviolet = generatedUrl ? generatedUrl : 'failure';
// Test to see if the document title for example.com has loaded, // Test to see if the document title for example.com has loaded,
// by appending an IFrame to the document and grabbing its content. // by appending an IFrame to the document and grabbing its content.
const testGeneratedUrlHacky = async (url) => { const testGeneratedUrlHacky = async (url) => {
let result = false; let result = false;
const exampleIFrame = document.createElement('iframe'); const exampleIFrame = document.createElement('iframe');
@ -311,7 +311,7 @@ xx xx
} }
}; };
// Run tests for Rammerhead and Ultraviolet // Run tests for Rammerhead and Ultraviolet.
const rammerheadPassed = await testRammerhead(); const rammerheadPassed = await testRammerhead();
const ultravioletPassed = await testUltraviolet(); const ultravioletPassed = await testUltraviolet();

View file

@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
import { build } from 'esbuild'; import { build } from 'esbuild';
import ecosystem from './ecosystem.config.js'; import ecosystem from './ecosystem.config.js';
// Some necessary constants are copied over from /src/server.mjs. // Some necessary constants are copied over from /src/server.mjs.
const config = Object.freeze( const config = Object.freeze(
JSON.parse(await readFile(new URL('./src/config.json', import.meta.url))) JSON.parse(await readFile(new URL('./src/config.json', import.meta.url)))
@ -27,12 +27,12 @@ const serverUrl = ((base) => {
const shutdown = fileURLToPath(new URL('./src/.shutdown', import.meta.url)); const shutdown = fileURLToPath(new URL('./src/.shutdown', import.meta.url));
// Run each command line argument passed after node run-command.mjs. // Run each command line argument passed after node run-command.mjs.
// Commands are defined in the switch case statement below. // Commands are defined in the switch case statement below.
commands: for (let i = 2; i < process.argv.length; i++) commands: for (let i = 2; i < process.argv.length; i++)
switch (process.argv[i]) { switch (process.argv[i]) {
// Commmand to boot up the server. Use PM2 to run if production is true in the // Commmand to boot up the server. Use PM2 to run if production is true in the
// config file. // config file.
case 'start': case 'start':
if (config.production) if (config.production)
exec( exec(
@ -42,8 +42,8 @@ commands: for (let i = 2; i < process.argv.length; i++)
console.log(stdout); console.log(stdout);
} }
); );
// Handle setup on Windows differently from platforms with POSIX-compliant shells. // Handle setup on Windows differently from platforms with POSIX-compliant
// This should run the server as a background process. // shells. This should run the server as a background process.
else if (process.platform === 'win32') else if (process.platform === 'win32')
exec('START /MIN "" node backend.js', (error, stdout) => { exec('START /MIN "" node backend.js', (error, stdout) => {
if (error) { if (error) {
@ -52,48 +52,29 @@ commands: for (let i = 2; i < process.argv.length; i++)
} }
console.log(stdout); console.log(stdout);
}); });
// The following approach (and similar approaches) will not work on Windows, // The following approach (and similar approaches) will not work on Windows,
// because exiting this program will also terminate backend.js on Windows. // because exiting this program will also terminate backend.js on Windows.
else { else {
const server = fork( const server = fork(
fileURLToPath(new URL('./backend.js', import.meta.url)), fileURLToPath(new URL('./backend.js', import.meta.url)),
{ { cwd: process.cwd(), detached: true }
cwd: process.cwd(),
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
detached: true
}
); );
server.stderr.on("data", stderr => {
console.error(stderr.toString());
if (stderr.toString().indexOf("DeprecationWarning") + 1)
return;
server.stderr.destroy();
process.exitCode = 1;
});
server.stdout.on("data", () => {
server.kill();
const server2 = fork(
fileURLToPath(new URL('./backend.js', import.meta.url)),
{cwd: process.cwd(), detached: true}
);
server2.unref();
server2.disconnect();
});
server.unref(); server.unref();
server.disconnect(); server.disconnect();
} }
break; break;
// Stop the server. Make a temporary file that the server will check for if told // Stop the server. Make a temporary file that the server will check for if told
// to shut down. This is done by sending a GET request to the server. // to shut down. This is done by sending a GET request to the server.
case 'stop': { case 'stop': {
await writeFile(shutdown, ''); await writeFile(shutdown, '');
let timeoutId, let timeoutId,
hasErrored = false; hasErrored = false;
try { try {
// Give the server 5 seconds to respond, otherwise cancel this and throw an /* Give the server 5 seconds to respond, otherwise cancel this and throw an
// error to the console. The fetch request will also throw an error immediately * error to the console. The fetch request will also throw an error
// if checking the server on localhost and the port is unused. * immediately if checking the server on localhost and the port is unused.
*/
const response = await Promise.race([ const response = await Promise.race([
fetch(new URL('/test-shutdown', serverUrl)), fetch(new URL('/test-shutdown', serverUrl)),
new Promise((resolve) => { new Promise((resolve) => {
@ -105,18 +86,18 @@ commands: for (let i = 2; i < process.argv.length; i++)
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (response === 'Error') throw new Error('Server is unresponsive.'); if (response === 'Error') throw new Error('Server is unresponsive.');
} catch (e) { } catch (e) {
// Remove the temporary shutdown file since the server didn't remove it. // Remove the temporary shutdown file since the server didn't remove it.
await unlink(shutdown); await unlink(shutdown);
// Check if this is the error thrown by the fetch request for an unused port. // Check if this is the error thrown by the fetch request for an unused port.
// Don't print the unused port error, since nothing has actually broken. // Don't print the unused port error, since nothing has actually broken.
if (e instanceof TypeError) clearTimeout(timeoutId); if (e instanceof TypeError) clearTimeout(timeoutId);
else { else {
console.error(e); console.error(e);
// Stop here unless Node will be killed later. // Stop here unless Node will be killed later.
if (!process.argv.slice(i + 1).includes('kill')) hasErrored = true; if (!process.argv.slice(i + 1).includes('kill')) hasErrored = true;
} }
} }
// Do not run this if Node will be killed later in this script. It will fail. // Do not run this if Node will be killed later in this script. It will fail.
if (config.production && !process.argv.slice(i + 1).includes('kill')) if (config.production && !process.argv.slice(i + 1).includes('kill'))
exec('npx pm2 stop ecosystem.config.js', (error, stdout) => { exec('npx pm2 stop ecosystem.config.js', (error, stdout) => {
if (error) { if (error) {
@ -125,8 +106,8 @@ commands: for (let i = 2; i < process.argv.length; i++)
} }
console.log(stdout); console.log(stdout);
}); });
// Do not continue executing commands since the server was unable to be stopped. // Do not continue executing commands since the server was unable to be stopped.
// Mostly implemented to prevent duplicating Node instances with npm restart. // Mostly implemented to prevent duplicating Node instances with npm restart.
if (hasErrored) { if (hasErrored) {
process.exitCode = 1; process.exitCode = 1;
break commands; break commands;
@ -154,9 +135,10 @@ commands: for (let i = 2; i < process.argv.length; i++)
break; break;
} }
// Kill all node processes and fully reset PM2. To be used for debugging. /* Kill all node processes and fully reset PM2. To be used for debugging.
// Using npx pm2 monit, or npx pm2 list in the terminal will also bring up * Using npx pm2 monit, or npx pm2 list in the terminal will also bring up
// more PM2 debugging tools. * more PM2 debugging tools.
*/
case 'kill': case 'kill':
if (process.platform === 'win32') if (process.platform === 'win32')
exec( exec(
@ -174,7 +156,57 @@ commands: for (let i = 2; i < process.argv.length; i++)
); );
break; break;
// No default case. case 'workflow':
if (config.production)
exec(
'npx pm2 start ecosystem.config.js --env production',
(error, stdout) => {
if (error) throw error;
console.log(stdout);
}
);
// Handle setup on Windows differently from platforms with POSIX-compliant
// shells. This should run the server as a background process.
else if (process.platform === 'win32')
exec('START /MIN "" node backend.js', (error, stdout) => {
if (error) {
console.error(error);
process.exitCode = 1;
}
console.log(stdout);
});
// The following approach (and similar approaches) will not work on Windows,
// because exiting this program will also terminate backend.js on Windows.
else {
const server = fork(
fileURLToPath(new URL('./backend.js', import.meta.url)),
{
cwd: process.cwd(),
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
detached: true,
}
);
server.stderr.on('data', (stderr) => {
console.error(stderr.toString());
if (stderr.toString().indexOf('DeprecationWarning') + 1) return;
server.stderr.destroy();
process.exitCode = 1;
});
server.stdout.on('data', () => {
server.kill();
const server2 = fork(
fileURLToPath(new URL('./backend.js', import.meta.url)),
{ cwd: process.cwd(), detached: true }
);
server2.unref();
server2.disconnect();
});
server.unref();
server.disconnect();
}
break;
// No default case.
} }
process.exitCode = process.exitCode || 0; process.exitCode = process.exitCode || 0;

View file

@ -13,7 +13,7 @@ import { libcurlPath } from '@mercuryworkshop/libcurl-transport';
import { bareModulePath } from '@mercuryworkshop/bare-as-module3'; import { bareModulePath } from '@mercuryworkshop/bare-as-module3';
import { baremuxPath } from '@mercuryworkshop/bare-mux/node'; import { baremuxPath } from '@mercuryworkshop/bare-mux/node';
import { uvPath } from '@titaniumnetwork-dev/ultraviolet'; import { uvPath } from '@titaniumnetwork-dev/ultraviolet';
// import { createBareServer } from "@tomphttp/bare-server-node"; // import { createBareServer } from "@tomphttp/bare-server-node";
const config = JSON.parse( const config = JSON.parse(
await readFile(new URL('./config.json', import.meta.url)) await readFile(new URL('./config.json', import.meta.url))
@ -23,7 +23,7 @@ const config = JSON.parse(
port = process.env.PORT || config.port, port = process.env.PORT || config.port,
app = express(), app = express(),
router = express.Router(), router = express.Router(),
// bare = createBareServer("/bare/"), // bare = createBareServer("/bare/"),
rh = createRammerhead(); rh = createRammerhead();
const rammerheadScopes = [ const rammerheadScopes = [
@ -60,10 +60,10 @@ const rammerheadSession = /^\/[a-z0-9]{32}/,
}, },
server = http.createServer((req, res) => { server = http.createServer((req, res) => {
/* /*
if (bare.shouldRoute(req)) { if (bare.shouldRoute(req)) {
bare.routeRequest(req, res); bare.routeRequest(req, res);
} else } else
*/ */
if (shouldRouteRh(req)) { if (shouldRouteRh(req)) {
routeRhRequest(req, res); routeRhRequest(req, res);
} else { } else {
@ -76,7 +76,7 @@ server.on('upgrade', (req, socket, head) => {
if (bare.shouldRoute(req)) { if (bare.shouldRoute(req)) {
bare.routeUpgrade(req, socket, head); bare.routeUpgrade(req, socket, head);
} else } else
*/ */
if (shouldRouteRh(req)) { if (shouldRouteRh(req)) {
routeRhUpgrade(req, socket, head); routeRhUpgrade(req, socket, head);
} else if (req.url.endsWith('/wisp/')) { } else if (req.url.endsWith('/wisp/')) {
@ -84,17 +84,18 @@ server.on('upgrade', (req, socket, head) => {
} }
}); });
// Apply Helmet middleware for security // Apply Helmet middleware for security.
app.use( app.use(
helmet({ helmet({
contentSecurityPolicy: false, // Disable CSP contentSecurityPolicy: false, // Disable CSP
}) })
); );
// All website files are stored in the /views directory. /* All website files are stored in the /views directory.
// This takes one of those files and displays it for a site visitor. * This takes one of those files and displays it for a site visitor.
// Query strings like /?j are converted into paths like /views/hidden.html * Query strings like /?j are converted into paths like /views/hidden.html
// back here. Which query string converts to what is defined in routes.mjs. * back here. Which query string converts to what is defined in routes.mjs.
*/
router.get('/', async (req, res) => router.get('/', async (req, res) =>
res.send( res.send(
paintSource( paintSource(
@ -103,8 +104,8 @@ router.get('/', async (req, res) =>
path.join( path.join(
__dirname, __dirname,
'views', 'views',
// Return the error page if the query is not found in // Return the error page if the query is not found in
// routes.mjs. Also set index as the default page. // routes.mjs. Also set index as the default page.
'/?'.indexOf(req.url) '/?'.indexOf(req.url)
? pages[Object.keys(req.query)[0]] || 'error.html' ? pages[Object.keys(req.query)[0]] || 'error.html'
: pages.index : pages.index
@ -125,7 +126,7 @@ app.use('/baremux/', express.static(baremuxPath));
app.disable('x-powered-by'); app.disable('x-powered-by');
// Redundant code since 404 is handled elsewhere; left here as insurance. // Redundant code since 404 is handled elsewhere; left here as insurance.
app.use((req, res) => { app.use((req, res) => {
res.status(404).send(paintSource(loadTemplates(text404))); res.status(404).send(paintSource(loadTemplates(text404)));
}); });

View file

@ -11,9 +11,10 @@ const {
text404, text404,
} = pkg; } = pkg;
// Below are lots of function definitions used to obfuscate the website. /* Below are lots of function definitions used to obfuscate the website.
// This makes the website harder to properly categorize, as its source code * This makes the website harder to properly categorize, as its source code
// changes with each time it is loaded. * changes with each time it is loaded.
*/
const randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0], const randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0],
charset = /&#173;|&#8203;|&shy;|<wbr>/gi, charset = /&#173;|&#8203;|&shy;|<wbr>/gi,
getRandomChar = randomListItem(charRandom), getRandomChar = randomListItem(charRandom),
@ -28,26 +29,25 @@ const randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0],
'<!-- IMPORTANT-HUTAOCOOKINGINSERT-DONOTDELETE -->', '<!-- IMPORTANT-HUTAOCOOKINGINSERT-DONOTDELETE -->',
getCookingText getCookingText
), ),
// This one isn't for obfuscation; it's just for dealing with cache issues. // This one isn't for obfuscation; it's just for dealing with cache issues.
cacheBusting = (str) => { cacheBusting = (str) => {
for (let item of Object.entries(cacheBustList)) for (let item of Object.entries(cacheBustList))
str = str.replaceAll(item[0], item[1]); str = str.replaceAll(item[0], item[1]);
return str; return str;
}, },
// Apply the final obfuscation changes to an entire file. // Apply the final obfuscation changes to an entire file.
paintSource = (str) => paintSource = (str) =>
insertCharset(hutaoInsert(versionInsert(insertCooking(cacheBusting(str))))), insertCharset(hutaoInsert(versionInsert(insertCooking(cacheBusting(str))))),
// Use this instead of text404 for a preloaded error page. // Use this instead of text404 for a preloaded error page.
preloaded404 = paintSource(text404), preloaded404 = paintSource(text404),
// Grab the text content of a file. Ensure the file is a string. // Grab the text content of a file. Ensure the file is a string.
tryReadFile = (file) => tryReadFile = (file) =>
existsSync(file + '') ? readFileSync(file + '', 'utf8') : preloaded404; existsSync(file + '') ? readFileSync(file + '', 'utf8') : preloaded404;
/* /*
// All of this is now old code.
// The newer versions of these functions are directly above. All of this is now old code.
*/ The newer versions of these functions are directly above.
/*
function randomListItem(lis) { function randomListItem(lis) {
return lis[Math.floor(Math.random() * lis.length)]; return lis[Math.floor(Math.random() * lis.length)];

View file

@ -27,9 +27,10 @@ const config = Object.freeze(
{ pages, externalPages } = pageRoutes, { pages, externalPages } = pageRoutes,
__dirname = path.resolve(); __dirname = path.resolve();
// Record the server's location as a URL object, including its host and port. /* Record the server's location as a URL object, including its host and port.
// The host can be modified at /src/config.json, whereas the ports can be modified * The host can be modified at /src/config.json, whereas the ports can be modified
// at /ecosystem.config.js. * at /ecosystem.config.js.
*/
const serverUrl = ((base) => { const serverUrl = ((base) => {
try { try {
base = new URL(config.host); base = new URL(config.host);
@ -43,8 +44,8 @@ const serverUrl = ((base) => {
})(); })();
console.log(serverUrl); console.log(serverUrl);
// The server will check for the existence of this file when a shutdown is requested. // The server will check for the existence of this file when a shutdown is requested.
// The shutdown script in run-command.js will temporarily produce this file. // The shutdown script in run-command.js will temporarily produce this file.
const shutdown = fileURLToPath(new URL('./.shutdown', import.meta.url)); const shutdown = fileURLToPath(new URL('./.shutdown', import.meta.url));
const rh = createRammerhead(); const rh = createRammerhead();
@ -85,7 +86,7 @@ const rammerheadSession = /^\/[a-z0-9]{32}/,
rh.emit('upgrade', req, socket, head); rh.emit('upgrade', req, socket, head);
}; };
// Create a server factory for RH, and wisp (and bare if you please). // Create a server factory for RH, and wisp (and bare if you please).
const serverFactory = (handler) => { const serverFactory = (handler) => {
return createServer() return createServer()
.on('request', (req, res) => { .on('request', (req, res) => {
@ -98,7 +99,7 @@ const serverFactory = (handler) => {
}); });
}; };
// Set logger to true for logs // Set logger to true for logs.
const app = Fastify({ const app = Fastify({
ignoreDuplicateSlashes: true, ignoreDuplicateSlashes: true,
ignoreTrailingSlash: true, ignoreTrailingSlash: true,
@ -106,13 +107,13 @@ const app = Fastify({
serverFactory: serverFactory, serverFactory: serverFactory,
}); });
// Apply Helmet middleware for security // Apply Helmet middleware for security.
app.register(fastifyHelmet, { app.register(fastifyHelmet, {
contentSecurityPolicy: false, // Disable CSP contentSecurityPolicy: false, // Disable CSP
xPoweredBy: false, xPoweredBy: false,
}); });
// Assign server file paths to different paths, for serving content on the website. // Assign server file paths to different paths, for serving content on the website.
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: fileURLToPath(new URL('../views/pages', import.meta.url)), root: fileURLToPath(new URL('../views/pages', import.meta.url)),
decorateReply: false, decorateReply: false,
@ -133,7 +134,7 @@ app.register(fastifyStatic, {
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: fileURLToPath( root: fileURLToPath(
new URL( new URL(
// Use the pre-compiled, minified scripts instead, if enabled in config. // Use the pre-compiled, minified scripts instead, if enabled in config.
config.minifyScripts ? '../views/dist/assets/js' : '../views/assets/js', config.minifyScripts ? '../views/dist/assets/js' : '../views/assets/js',
import.meta.url import.meta.url
) )
@ -145,7 +146,7 @@ app.register(fastifyStatic, {
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: fileURLToPath( root: fileURLToPath(
new URL( new URL(
// Use the pre-compiled, minified stylesheets instead, if enabled in config. // Use the pre-compiled, minified stylesheets instead, if enabled in config.
config.minifyScripts ? '../views/dist/assets/css' : '../views/assets/css', config.minifyScripts ? '../views/dist/assets/css' : '../views/assets/css',
import.meta.url import.meta.url
) )
@ -154,13 +155,13 @@ app.register(fastifyStatic, {
decorateReply: false, decorateReply: false,
}); });
// This combines scripts from the official UV repository with local UV scripts into // This combines scripts from the official UV repository with local UV scripts into
// one directory path. Local versions of files override the official versions. // one directory path. Local versions of files override the official versions.
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: [ root: [
fileURLToPath( fileURLToPath(
new URL( new URL(
// Use the pre-compiled, minified scripts instead, if enabled in config. // Use the pre-compiled, minified scripts instead, if enabled in config.
config.minifyScripts ? '../views/dist/uv' : '../views/uv', config.minifyScripts ? '../views/dist/uv' : '../views/uv',
import.meta.url import.meta.url
) )
@ -171,7 +172,7 @@ app.register(fastifyStatic, {
decorateReply: false, decorateReply: false,
}); });
// Register proxy paths to the website. // Register proxy paths to the website.
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: epoxyPath, root: epoxyPath,
prefix: '/epoxy/', prefix: '/epoxy/',
@ -196,12 +197,13 @@ app.register(fastifyStatic, {
decorateReply: false, decorateReply: false,
}); });
// All website files are stored in the /views directory. /* All website files are stored in the /views directory.
// This takes one of those files and displays it for a site visitor. * This takes one of those files and displays it for a site visitor.
// Paths like /browsing are converted into paths like /views/pages/surf.html * Paths like /browsing are converted into paths like /views/pages/surf.html
// back here. Which path converts to what is defined in routes.mjs. * back here. Which path converts to what is defined in routes.mjs.
*/
app.get('/:path', (req, reply) => { app.get('/:path', (req, reply) => {
// Testing for future features that need cookies to deliver alternate source files. // Testing for future features that need cookies to deliver alternate source files.
if (req.raw.rawHeaders.includes('Cookie')) if (req.raw.rawHeaders.includes('Cookie'))
console.log(req.raw.rawHeaders[req.raw.rawHeaders.indexOf('Cookie') + 1]); console.log(req.raw.rawHeaders[req.raw.rawHeaders.indexOf('Cookie') + 1]);
@ -214,8 +216,8 @@ app.get('/:path', (req, reply) => {
return reply.redirect(externalRoute); return reply.redirect(externalRoute);
} }
// If a GET request is sent to /test-shutdown and a script-generated shutdown file // If a GET request is sent to /test-shutdown and a script-generated shutdown file
// is present, gracefully shut the server down. // is present, gracefully shut the server down.
if (reqPath === 'test-shutdown' && existsSync(shutdown)) { if (reqPath === 'test-shutdown' && existsSync(shutdown)) {
console.log('Holy Unblocker is shutting down.'); console.log('Holy Unblocker is shutting down.');
app.close(); app.close();
@ -223,7 +225,7 @@ app.get('/:path', (req, reply) => {
process.exitCode = 0; process.exitCode = 0;
} }
// Return the error page if the query is not found in routes.mjs. // Return the error page if the query is not found in routes.mjs.
if (reqPath && !(reqPath in pages)) if (reqPath && !(reqPath in pages))
return reply.code(404).type('text/html').send(preloaded404); return reply.code(404).type('text/html').send(preloaded404);
@ -234,7 +236,7 @@ app.get('/:path', (req, reply) => {
path.join( path.join(
__dirname, __dirname,
'views', 'views',
// Set the index the as the default page. // Set the index the as the default page.
reqPath ? pages[reqPath] : pages.index reqPath ? pages[reqPath] : pages.index
) )
) )
@ -259,7 +261,7 @@ app.get("/assets/js/uv/uv.config.js", (req, reply) => {
}); });
*/ */
// Set an error page for invalid paths outside the query string system. // Set an error page for invalid paths outside the query string system.
app.setNotFoundHandler((req, reply) => { app.setNotFoundHandler((req, reply) => {
reply.code(404).type('text/html').send(preloaded404); reply.code(404).type('text/html').send(preloaded404);
}); });

View file

@ -15,11 +15,11 @@ const header = tryReadFile(path.normalize(__dirname + '/header.html')),
.replace('<!--HEADER-->', header) .replace('<!--HEADER-->', header)
.replace('<!--FOOTER-->', footer) .replace('<!--FOOTER-->', footer)
// Used only on docs.html // Used only on docs.html
.replace('<!--DOCS-->', documentation) .replace('<!--DOCS-->', documentation)
// Used only on faq.html // Used only on faq.html
.replace('<!--FAQ-->', faq) .replace('<!--FAQ-->', faq)
// Used only on terms.html // Used only on terms.html
.replace('<!--TOS-->', terms) .replace('<!--TOS-->', terms)
// Used only on header.html // Used only on header.html
.replace('<!--SETTINGS-->', settings); .replace('<!--SETTINGS-->', settings);

View file

@ -4,34 +4,35 @@
/* Card Shimmer Mouse Follow Script /* Card Shimmer Mouse Follow Script
/* ----------------------------------------------- */ /* ----------------------------------------------- */
// Encase everything in a new scope so that variables are not accidentally // Encase everything in a new scope so that variables are not accidentally
// attached to the global scope. // attached to the global scope.
(() => { (() => {
// Track the cursor position with respect to the top left of the card. // Track the cursor position with respect to the top left of the card.
// The "this" keyword gets the element that invoked the event listener. // The "this" keyword gets the element that invoked the event listener.
const handleMouseMove = (element) => { const handleMouseMove = (element) => {
element.addEventListener('mousemove', (e) => { element.addEventListener('mousemove', (e) => {
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
const x = e.clientX - rect.left; const x = e.clientX - rect.left;
const y = e.clientY - rect.top; const y = e.clientY - rect.top;
element.style.setProperty('--mouse-x', `${x}px`); element.style.setProperty('--mouse-x', `${x}px`);
element.style.setProperty('--mouse-y', `${y}px`); element.style.setProperty('--mouse-y', `${y}px`);
}); });
}, },
// Reset the cursor tracking variables when the cursor leaves the card. // Reset the cursor tracking variables when the cursor leaves the card.
handleMouseLeave = (element) => { handleMouseLeave = (element) => {
element.addEventListener('mouseleave', () => { element.addEventListener('mouseleave', () => {
element.style.setProperty('--mouse-x', `50%`); element.style.setProperty('--mouse-x', `50%`);
element.style.setProperty('--mouse-y', `50%`); element.style.setProperty('--mouse-y', `50%`);
}); });
}, },
// Get the box card elements and add the event listeners to them. // Get the box card elements and add the event listeners to them.
shimmerEffects = document.querySelectorAll('.box-card, .box-hero'); shimmerEffects = document.querySelectorAll('.box-card, .box-hero');
// Attach CSS variables, mouse-x and mouse-y, to elements that will be /* Attach CSS variables, mouse-x and mouse-y, to elements that will be
// given shimmer effects, by adding or modifying the style attribute. * given shimmer effects, by adding or modifying the style attribute.
// CSS calculates and renders the actual shimmer effect from there. * CSS calculates and renders the actual shimmer effect from there.
shimmerEffects.forEach(handleMouseMove); */
shimmerEffects.forEach(handleMouseLeave); shimmerEffects.forEach(handleMouseMove);
shimmerEffects.forEach(handleMouseLeave);
})(); })();

File diff suppressed because it is too large Load diff

View file

@ -5,173 +5,168 @@
/* Settings Menu /* Settings Menu
/* ----------------------------------------------- */ /* ----------------------------------------------- */
// Encase everything in a new scope so that variables are not accidentally // Encase everything in a new scope so that variables are not accidentally
// attached to the global scope. // attached to the global scope.
(() => { (() => {
// Determine the expiration date of a new cookie. // Determine the expiration date of a new cookie.
let date = new Date(); let date = new Date();
date.setFullYear(date.getFullYear() + 100); date.setFullYear(date.getFullYear() + 100);
date = date.toUTCString(); date = date.toUTCString();
// All cookies should be secure and are intended to work in iframes. // All cookies should be secure and are intended to work in iframes.
const setCookie = (name, value) => { const setCookie = (name, value) => {
document.cookie = document.cookie =
name + name +
`=${encodeURIComponent(value)}; expires=${date}; SameSite=None; Secure;`; `=${encodeURIComponent(value)}; expires=${date}; SameSite=None; Secure;`;
}, },
removeCookie = (name) => { removeCookie = (name) => {
document.cookie = document.cookie =
name + name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure;';
'=; expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure;'; },
}, readCookie = async (name) => {
readCookie = async (name) => { // Get the first cookie that has the same name.
// Get the first cookie that has the same name. for (let cookie of document.cookie.split('; '))
for (let cookie of document.cookie.split('; ')) if (!cookie.indexOf(name + '='))
if (!cookie.indexOf(name + '=')) // Return the cookie's stored content.
// Return the cookie's stored content. return decodeURIComponent(cookie.slice(name.length + 1));
return decodeURIComponent(cookie.slice(name.length + 1)); },
}, // Customize the page's title.
// Customize the page's title. pageTitle = (value) => {
pageTitle = (value) => { let tag =
let tag = document.getElementsByTagName('title')[0] ||
document.getElementsByTagName('title')[0] || document.createElement('title');
document.createElement('title'); tag.innerHTML = value;
tag.innerHTML = value; document.head.appendChild(tag);
document.head.appendChild(tag); },
}, // Set the page's favicon to a new URL.
// Set the page's favicon to a new URL. pageIcon = (value) => {
pageIcon = (value) => { let tag =
let tag = document.querySelector("link[rel*='icon']") ||
document.querySelector("link[rel*='icon']") || document.createElement('link');
document.createElement('link'); tag.rel = 'icon';
tag.rel = 'icon'; tag.href = value;
tag.href = value; document.head.appendChild(tag);
document.head.appendChild(tag); },
}, // Make a small stylesheet to override a setting from the main stylesheet.
// Make a small stylesheet to override a setting from the main stylesheet. pageShowAds = () => {
pageShowAds = () => { let advertising = document.createElement('style');
let advertising = document.createElement('style'); advertising.id = 'advertising';
advertising.id = 'advertising'; advertising.innerText = '.ad { display:block; }';
advertising.innerText = '.ad { display:block; }'; (
document.head ||
document.body ||
document.documentElement ||
document
).appendChild(advertising);
},
// Remove the stylesheet made by the function above, if it exists.
pageHideAds = () => {
(document.getElementById('advertising') || new Text()).remove();
},
// These titles and icons are used as autofill templates by settings.html.
// The icon URLs and tab titles may need to be updated over time.
presetIcons = Object.freeze({
'': ' \n ',
Google: 'Google \n https://www.google.com/favicon.ico',
Bing: 'Bing \n https://www.bing.com/sa/simg/favicon-trans-bg-blue-mg-28.ico',
'Google Drive':
'Home - Google Drive \n https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png',
Gmail:
'Inbox - Gmail \n https://ssl.gstatic.com/ui/v1/icons/mail/rfr/gmail.ico',
}),
// Choose the default transport mode, for proxying, based on the browser.
// Firefox is not supported by epoxy yet, which is why this is implemented.
defaultMode = /(?:Chrome|AppleWebKit)\//.test(navigator.userAgent)
? 'epoxy'
: 'libcurl';
// Load a custom page title and favicon if it was previously stored.
readCookie('HBTitle').then((s) => {
s != undefined && pageTitle(s);
});
readCookie('HBIcon').then((s) => {
s != undefined && pageIcon(s);
});
// Load the UV transport mode that was last used, or use the default.
readCookie('HBTransport').then((s) => {
let transportMode = document.querySelector(
`#uv-transport-list input[value="${s || defaultMode}"]`
);
if (transportMode) transportMode.click();
});
// Ads are disabled by default. Load ads if ads were enabled previously.
// Change !== to === here if ads should be enabled by default.
readCookie('HBHideAds').then((s) => {
s !== 'false'
? pageHideAds()
: pageShowAds(((document.getElementById('hideads') || {}).checked = 0));
});
// Tor is disabled by default. Enable Tor if it was enabled previously.
readCookie('HBUseOnion').then((s) => {
if (s === 'true') {
let torCheck = document.getElementById('useonion') || {
dispatchEvent: () => {},
};
torCheck.checked = 1;
torCheck.dispatchEvent(new Event('change'));
}
});
// All code below is used by the Settings UI in the navigation bar.
if (document.getElementById('csel')) {
const attachEventListener = (selector, ...args) =>
( (
document.head || document.getElementById(selector) || document.querySelector(selector)
document.body || ).addEventListener(...args),
document.documentElement || focusElement = document
document .getElementsByClassName('dropdown-settings')[0]
).appendChild(advertising); .parentElement.querySelector("a[href='#']");
},
// Remove the stylesheet made by the function above, if it exists.
pageHideAds = () => {
(document.getElementById('advertising') || new Text()).remove();
},
// These titles and icons are used as autofill templates by settings.html.
// The icon URLs and tab titles may need to be updated over time.
presetIcons = Object.freeze({
'': ' \n ',
Google: 'Google \n https://www.google.com/favicon.ico',
Bing: 'Bing \n https://www.bing.com/sa/simg/favicon-trans-bg-blue-mg-28.ico',
'Google Drive':
'Home - Google Drive \n https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png',
Gmail:
'Inbox - Gmail \n https://ssl.gstatic.com/ui/v1/icons/mail/rfr/gmail.ico',
}),
// Choose the default transport mode, for proxying, based on the browser.
// Firefox is not supported by epoxy yet, which is why this is implemented.
defaultMode = /(?:Chrome|AppleWebKit)\//.test(navigator.userAgent)
? 'epoxy'
: 'libcurl';
// Load a custom page title and favicon if it was previously stored. attachEventListener('.dropdown-settings .close-settings-btn', 'click', () => {
readCookie('HBTitle').then((s) => { document.activeElement.blur();
s != undefined && pageTitle(s);
});
readCookie('HBIcon').then((s) => {
s != undefined && pageIcon(s);
}); });
// Load the UV transport mode that was last used, or use the default. // Allow users to set a custom title with the UI.
readCookie('HBTransport').then((s) => { attachEventListener('titleform', 'submit', (e) => {
let transportMode = document.querySelector( e.preventDefault();
`#uv-transport-list input[value="${s || defaultMode}"]` e = e.target.firstElementChild;
); if (e.value) {
if (transportMode) transportMode.click(); pageTitle(e.value);
}); setCookie('HBTitle', e.value);
e.value = '';
// Ads are disabled by default. Load ads if ads were enabled previously. } else if (confirm('Reset the title to default?')) {
// Change !== to === here if ads should be enabled by default. // Allow users to reset the title to default if nothing is entered.
readCookie('HBHideAds').then((s) => { focusElement.focus();
s !== 'false' removeCookie('HBTitle');
? pageHideAds() pageTitle('Holy Unblocker LTS');
: pageShowAds(((document.getElementById('hideads') || {}).checked = 0));
});
// Tor is disabled by default. Enable Tor if it was enabled previously.
readCookie('HBUseOnion').then((s) => {
if (s === 'true') {
let torCheck = document.getElementById('useonion') || {
dispatchEvent: () => {},
};
torCheck.checked = 1;
torCheck.dispatchEvent(new Event('change'));
} }
}); });
// All code below is used by the Settings UI in the navigation bar. // Allow users to set a custom favicon with the UI.
if (document.getElementById('csel')) { attachEventListener('iconform', 'submit', (e) => {
const attachEventListener = (selector, ...args) => e.preventDefault();
( e = e.target.firstElementChild;
document.getElementById(selector) || document.querySelector(selector) if (e.value) {
).addEventListener(...args), pageIcon(e.value);
focusElement = document setCookie('HBIcon', e.value);
.getElementsByClassName('dropdown-settings')[0] e.value = '';
.parentElement.querySelector("a[href='#']"); } else if (confirm('Reset the icon to default?')) {
// Allow users to reset the favicon to default if nothing is entered.
focusElement.focus();
removeCookie('HBIcon');
pageIcon('assets/img/icon.png');
}
});
attachEventListener( /*
'.dropdown-settings .close-settings-btn',
'click',
() => {
document.activeElement.blur();
}
);
// Allow users to set a custom title with the UI.
attachEventListener('titleform', 'submit', (e) => {
e.preventDefault();
e = e.target.firstElementChild;
if (e.value) {
pageTitle(e.value);
setCookie('HBTitle', e.value);
e.value = '';
} else if (confirm('Reset the title to default?')) {
// Allow users to reset the title to default if nothing is entered.
focusElement.focus();
removeCookie('HBTitle');
pageTitle('Holy Unblocker LTS');
}
});
// Allow users to set a custom favicon with the UI.
attachEventListener('iconform', 'submit', (e) => {
e.preventDefault();
e = e.target.firstElementChild;
if (e.value) {
pageIcon(e.value);
setCookie('HBIcon', e.value);
e.value = '';
} else if (confirm('Reset the icon to default?')) {
// Allow users to reset the favicon to default if nothing is entered.
focusElement.focus();
removeCookie('HBIcon');
pageIcon('assets/img/icon.png');
}
});
/*
This is unused in the current settings menu. This is unused in the current settings menu.
// Allow users to make a new about:blank tab and view the site from there. // Allow users to make a new about:blank tab and view the site from there.
// An iframe of the current page is inserted into the new tab. // An iframe of the current page is inserted into the new tab.
attachEventListener("cselab", "click", () => { attachEventListener("cselab", "click", () => {
let win = window.open(); let win = window.open();
let iframe = win.document.createElement("iframe"); let iframe = win.document.createElement("iframe");
@ -179,70 +174,71 @@
iframe.src = location.href; iframe.src = location.href;
win.document.body.appendChild(iframe); win.document.body.appendChild(iframe);
}); });
*/ */
// Provides users with a handy set of title and icon autofill options. // Provides users with a handy set of title and icon autofill options.
attachEventListener('icon-list', 'change', (e) => { attachEventListener('icon-list', 'change', (e) => {
let titleform = document.getElementById('titleform'), let titleform = document.getElementById('titleform'),
iconform = document.getElementById('iconform'); iconform = document.getElementById('iconform');
[titleform.firstElementChild.value, iconform.firstElementChild.value] = ( [titleform.firstElementChild.value, iconform.firstElementChild.value] = (
presetIcons[e.target.value] || ' \n ' presetIcons[e.target.value] || ' \n '
).split(' \n '); ).split(' \n ');
});
// Allow users to change the UV transport mode, for proxying, with the UI.
const uvTransportList = document.getElementById('uv-transport-list');
uvTransportList.querySelectorAll('input').forEach((element) => {
element.addEventListener('change', (e) => {
!uvTransportList.querySelector('input:checked') ||
e.target.value === defaultMode
? removeCookie('HBTransport')
: setCookie('HBTransport', e.target.value);
// Only the libcurl transport mode supports Tor at the moment.
let torCheck = document.getElementById('useonion');
if (e.target.value !== 'libcurl' && torCheck.checked) torCheck.click();
}); });
});
// Allow users to change the UV transport mode, for proxying, with the UI. // Allow users to toggle ads with the UI.
const uvTransportList = document.getElementById('uv-transport-list'); attachEventListener('hideads', 'change', (e) => {
uvTransportList.querySelectorAll('input').forEach((element) => { if (e.target.checked) {
element.addEventListener('change', (e) => { pageHideAds();
!uvTransportList.querySelector('input:checked') || setCookie('HBHideAds', 'true');
e.target.value === defaultMode } else {
? removeCookie('HBTransport') pageShowAds();
: setCookie('HBTransport', e.target.value); setCookie('HBHideAds', 'false');
}
});
// Only the libcurl transport mode supports Tor at the moment. /* Allow users to toggle onion routing in Ultraviolet with the UI. Only
let torCheck = document.getElementById('useonion'); * the libcurl transport mode supports Tor at the moment, so ensure that
if (e.target.value !== 'libcurl' && torCheck.checked) torCheck.click(); * users are aware that they cannot use Tor with other modes.
}); */
}); attachEventListener('useonion', 'change', (e) => {
let unselectedModes = document.querySelectorAll(
// Allow users to toggle ads with the UI. '#uv-transport-list input:not([value=libcurl])'
attachEventListener('hideads', 'change', (e) => { );
if (e.target.checked) { if (e.target.checked) {
pageHideAds(); let selectedMode = document.querySelector(
setCookie('HBHideAds', 'true'); '#uv-transport-list input[value=libcurl]'
} else {
pageShowAds();
setCookie('HBHideAds', 'false');
}
});
// Allow users to toggle onion routing in Ultraviolet with the UI. Only
// the libcurl transport mode supports Tor at the moment, so ensure that
// users are aware that they cannot use Tor with other modes.
attachEventListener('useonion', 'change', (e) => {
let unselectedModes = document.querySelectorAll(
'#uv-transport-list input:not([value=libcurl])'
); );
if (e.target.checked) { unselectedModes.forEach((e) => {
let selectedMode = document.querySelector( e.setAttribute('disabled', 'true');
'#uv-transport-list input[value=libcurl]' });
); selectedMode.click();
unselectedModes.forEach((e) => { setCookie('HBUseOnion', 'true');
e.setAttribute('disabled', 'true'); } else {
}); unselectedModes.forEach((e) => {
selectedMode.click(); e.removeAttribute('disabled');
setCookie('HBUseOnion', 'true'); });
} else {
unselectedModes.forEach((e) => {
e.removeAttribute('disabled');
});
// Tor will likely never be enabled by default, so removing the cookie // Tor will likely never be enabled by default, so removing the cookie
// here may be better than setting it to false. // here may be better than setting it to false.
removeCookie('HBUseOnion'); removeCookie('HBUseOnion');
} }
}); });
} }
})(); })();
/* ----------------------------------------------- /* -----------------------------------------------

View file

@ -1,83 +1,84 @@
// Encase everything in a new scope so that variables are not accidentally // Encase everything in a new scope so that variables are not accidentally
// attached to the global scope. // attached to the global scope.
(() => { (() => {
const stockSW = '/uv/sw.js', const stockSW = '/uv/sw.js',
blacklistSW = '/uv/sw-blacklist.js', blacklistSW = '/uv/sw-blacklist.js',
swAllowedHostnames = ['localhost', '127.0.0.1'], swAllowedHostnames = ['localhost', '127.0.0.1'],
connection = new BareMux.BareMuxConnection('/baremux/worker.js'), connection = new BareMux.BareMuxConnection('/baremux/worker.js'),
wispUrl = wispUrl =
(location.protocol === 'https:' ? 'wss' : 'ws') + (location.protocol === 'https:' ? 'wss' : 'ws') +
'://' + '://' +
location.host + location.host +
'/wisp/', '/wisp/',
// Proxy configuration // Proxy configuration
proxyUrl = 'socks5h://localhost:9050', // Replace with your proxy URL proxyUrl = 'socks5h://localhost:9050', // Replace with your proxy URL
transports = { transports = {
epoxy: '/epoxy/index.mjs', epoxy: '/epoxy/index.mjs',
libcurl: '/libcurl/index.mjs', libcurl: '/libcurl/index.mjs',
bare: '/baremux/index.mjs', bare: '/baremux/index.mjs',
}, },
// The following two variables are copied and pasted here from csel.js. // The following two variables are copied and pasted here from csel.js.
readCookie = async (name) => { readCookie = async (name) => {
// Get the first cookie that has the same name. // Get the first cookie that has the same name.
for (let cookie of document.cookie.split('; ')) for (let cookie of document.cookie.split('; '))
if (!cookie.indexOf(name + '=')) if (!cookie.indexOf(name + '='))
// Return the cookie's stored content. // Return the cookie's stored content.
return decodeURIComponent(cookie.slice(name.length + 1)); return decodeURIComponent(cookie.slice(name.length + 1));
}, },
// Sets the default transport mode based on the browser. Firefox is not // Sets the default transport mode based on the browser. Firefox is not
// supported by epoxy yet, which is why this is implemented. // supported by epoxy yet, which is why this is implemented.
defaultMode = /(?:Chrome|AppleWebKit)\//.test(navigator.userAgent) defaultMode = /(?:Chrome|AppleWebKit)\//.test(navigator.userAgent)
? 'epoxy' ? 'epoxy'
: 'libcurl'; : 'libcurl';
transports.default = transports[defaultMode]; transports.default = transports[defaultMode];
// Prevent the transports object from accidentally being edited. // Prevent the transports object from accidentally being edited.
Object.freeze(transports); Object.freeze(transports);
const registerSW = async () => { const registerSW = async () => {
if (!navigator.serviceWorker) { if (!navigator.serviceWorker) {
if ( if (
location.protocol !== 'https:' && location.protocol !== 'https:' &&
!swAllowedHostnames.includes(location.hostname) !swAllowedHostnames.includes(location.hostname)
) )
throw new Error('Service workers cannot be registered without https.'); throw new Error('Service workers cannot be registered without https.');
throw new Error("Your browser doesn't support service workers."); throw new Error("Your browser doesn't support service workers.");
} }
// If the user has changed the transport mode, use that over the default. // If the user has changed the transport mode, use that over the default.
const transportMode = const transportMode =
transports[await readCookie('HBTransport')] || transports.default; transports[await readCookie('HBTransport')] || transports.default;
let transportOptions = { wisp: wispUrl }; let transportOptions = { wisp: wispUrl };
// Only use Tor with the proxy if the user has enabled it in settings. // Only use Tor with the proxy if the user has enabled it in settings.
if ((await readCookie('HBUseOnion')) === 'true') if ((await readCookie('HBUseOnion')) === 'true')
transportOptions.proxy = proxyUrl; transportOptions.proxy = proxyUrl;
await connection.setTransport(transportMode, [transportOptions]); await connection.setTransport(transportMode, [transportOptions]);
// Choose a service worker to register based on whether or not the user /* Choose a service worker to register based on whether or not the user
// has ads enabled. If the user changes this setting, this script needs * has ads enabled. If the user changes this setting, this script needs
// to be reloaded for this to update, such as by refreshing the page. * to be reloaded for this to update, such as by refreshing the page.
const registrations = await navigator.serviceWorker.getRegistrations(), */
usedSW = const registrations = await navigator.serviceWorker.getRegistrations(),
(await readCookie('HBHideAds')) !== 'false' ? blacklistSW : stockSW; usedSW =
(await readCookie('HBHideAds')) !== 'false' ? blacklistSW : stockSW;
// Unregister a service worker if it isn't the one being used. // Unregister a service worker if it isn't the one being used.
for (const registration of registrations) for (const registration of registrations)
if ( if (
registration.active && registration.active &&
new URL(registration.active.scriptURL).pathname !== new URL(registration.active.scriptURL).pathname !==
new URL(usedSW, location.origin).pathname new URL(usedSW, location.origin).pathname
) )
await registration.unregister(); await registration.unregister();
await navigator.serviceWorker.register(usedSW); await navigator.serviceWorker.register(usedSW);
}; };
/* /*
Commented out upon discovering that a duplicate BareMux connection may be Commented out upon discovering that a duplicate BareMux connection may be
unnecessary; previously thought to have prevented issues with refreshing. unnecessary; previously thought to have prevented issues with refreshing.
@ -89,9 +90,9 @@ async function setupTransportOnLoad() {
} }
} }
// Run transport setup on page load. // Run transport setup on page load.
setupTransportOnLoad(); setupTransportOnLoad();
*/ */
registerSW(); registerSW();
})(); })();