diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24eddd6c..2f9af72f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: run: npm run build - name: Start server - run: npm run manual-start + run: npm run workflow-test - name: Test server response run: npm test @@ -53,7 +53,7 @@ jobs: run: npm run build - name: Start server - run: npm run manual-start + run: npm run workflow-test - name: Test server response run: npm test diff --git a/.prettierignore b/.prettierignore index dc555529..d9bf83a7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -/lib \ No newline at end of file +/lib +/views/assets/js/*.js \ No newline at end of file diff --git a/package.json b/package.json index 72f138c1..1ea70f7f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "manual-start": "node run-command.mjs start", "kill": "node run-command.mjs stop kill", "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": [ "proxy", diff --git a/proxyServiceValidator.js b/proxyServiceValidator.js index 56f7e13a..d8da52c9 100644 --- a/proxyServiceValidator.js +++ b/proxyServiceValidator.js @@ -1,5 +1,5 @@ -// 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). +// 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). const axios = require('axios'); 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'); omnibox = omnibox && omnibox.querySelector('input[type=text]'); if (omnibox) { try { - // Send an artificial input to the omnibox. The omnibox will create - // a proxy URL and leave it as the input value in response. + // Send an artificial input to the omnibox. The omnibox will create + // a proxy URL and leave it as the input value in response. const urlPath = 'example.com'; omnibox.value = urlPath; await omnibox.dispatchEvent( 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) => { if (omnibox.value !== urlPath) resolve(omnibox.value); else @@ -147,7 +147,7 @@ const testCommonJSOnPage = async () => { timeout = new Promise((resolve) => { 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]); console.log('Generated Rammerhead URL:', rammerheadUrl); results.rammerhead = rammerheadUrl ? rammerheadUrl : 'failure'; @@ -225,8 +225,8 @@ xx xx ? resolve() : window.addEventListener('load', resolve); - // Wait until a service worker is registered before continuing. - // Also make sure the document is loaded. + // Wait until a service worker is registered before continuing. + // Also make sure the document is loaded. const waitForWorker = async () => setTimeout(async () => { (await navigator.serviceWorker.getRegistrations()).length >= 1 @@ -237,32 +237,32 @@ xx xx waitForWorker(); }); - // Locate the omnibox element on the Ultraviolet page. + // Locate the omnibox element on the Ultraviolet page. let omnibox = document.getElementById('pr-uv'); omnibox = omnibox && omnibox.querySelector('input[type=text]'); 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 = { path: 'example.com', title: 'Example Domain', }; try { - // Send an artificial input to the omnibox. The omnibox will create - // a proxy URL and leave it as the input value in response. + // Send an artificial input to the omnibox. The omnibox will create + // a proxy URL and leave it as the input value in response. omnibox.value = website.path; await omnibox.dispatchEvent( 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; console.log('Generated Ultraviolet URL:', generatedUrl); results[0].ultraviolet = generatedUrl ? generatedUrl : 'failure'; - // Test to see if the document title for example.com has loaded, - // by appending an IFrame to the document and grabbing its content. + // Test to see if the document title for example.com has loaded, + // by appending an IFrame to the document and grabbing its content. const testGeneratedUrlHacky = async (url) => { let result = false; 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 ultravioletPassed = await testUltraviolet(); diff --git a/run-command.mjs b/run-command.mjs index 225a2abf..65d8887f 100644 --- a/run-command.mjs +++ b/run-command.mjs @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import { build } from 'esbuild'; 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( 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)); -// Run each command line argument passed after node run-command.mjs. -// Commands are defined in the switch case statement below. +// Run each command line argument passed after node run-command.mjs. +// Commands are defined in the switch case statement below. commands: for (let i = 2; i < process.argv.length; i++) switch (process.argv[i]) { - // Commmand to boot up the server. Use PM2 to run if production is true in the - // config file. + // Commmand to boot up the server. Use PM2 to run if production is true in the + // config file. case 'start': if (config.production) exec( @@ -42,8 +42,8 @@ commands: for (let i = 2; i < process.argv.length; i++) console.log(stdout); } ); - // Handle setup on Windows differently from platforms with POSIX-compliant shells. - // This should run the server as a background process. + // 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) { @@ -52,48 +52,29 @@ commands: for (let i = 2; i < process.argv.length; i++) } 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. + // 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 - } + { cwd: process.cwd(), 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; - // 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. + // 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. case 'stop': { await writeFile(shutdown, ''); let timeoutId, hasErrored = false; try { - // 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 - // if checking the server on localhost and the port is unused. + /* 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 if checking the server on localhost and the port is unused. + */ const response = await Promise.race([ fetch(new URL('/test-shutdown', serverUrl)), new Promise((resolve) => { @@ -105,18 +86,18 @@ commands: for (let i = 2; i < process.argv.length; i++) clearTimeout(timeoutId); if (response === 'Error') throw new Error('Server is unresponsive.'); } 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); - // 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. + // 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. if (e instanceof TypeError) clearTimeout(timeoutId); else { 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; } } - // 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')) exec('npx pm2 stop ecosystem.config.js', (error, stdout) => { if (error) { @@ -125,8 +106,8 @@ commands: for (let i = 2; i < process.argv.length; i++) } console.log(stdout); }); - // Do not continue executing commands since the server was unable to be stopped. - // Mostly implemented to prevent duplicating Node instances with npm restart. + // Do not continue executing commands since the server was unable to be stopped. + // Mostly implemented to prevent duplicating Node instances with npm restart. if (hasErrored) { process.exitCode = 1; break commands; @@ -154,9 +135,10 @@ commands: for (let i = 2; i < process.argv.length; i++) break; } - // 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 - // more PM2 debugging tools. + /* 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 + * more PM2 debugging tools. + */ case 'kill': if (process.platform === 'win32') exec( @@ -174,7 +156,57 @@ commands: for (let i = 2; i < process.argv.length; i++) ); 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; \ No newline at end of file +process.exitCode = process.exitCode || 0; diff --git a/src/express.mjs b/src/express.mjs index b151a2fa..0a750635 100644 --- a/src/express.mjs +++ b/src/express.mjs @@ -13,7 +13,7 @@ import { libcurlPath } from '@mercuryworkshop/libcurl-transport'; import { bareModulePath } from '@mercuryworkshop/bare-as-module3'; import { baremuxPath } from '@mercuryworkshop/bare-mux/node'; import { uvPath } from '@titaniumnetwork-dev/ultraviolet'; -// import { createBareServer } from "@tomphttp/bare-server-node"; +// import { createBareServer } from "@tomphttp/bare-server-node"; const config = JSON.parse( await readFile(new URL('./config.json', import.meta.url)) @@ -23,7 +23,7 @@ const config = JSON.parse( port = process.env.PORT || config.port, app = express(), router = express.Router(), - // bare = createBareServer("/bare/"), + // bare = createBareServer("/bare/"), rh = createRammerhead(); const rammerheadScopes = [ @@ -60,10 +60,10 @@ const rammerheadSession = /^\/[a-z0-9]{32}/, }, server = http.createServer((req, res) => { /* - if (bare.shouldRoute(req)) { - bare.routeRequest(req, res); - } else -*/ + if (bare.shouldRoute(req)) { + bare.routeRequest(req, res); + } else + */ if (shouldRouteRh(req)) { routeRhRequest(req, res); } else { @@ -76,7 +76,7 @@ server.on('upgrade', (req, socket, head) => { if (bare.shouldRoute(req)) { bare.routeUpgrade(req, socket, head); } else -*/ + */ if (shouldRouteRh(req)) { routeRhUpgrade(req, socket, head); } 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( helmet({ contentSecurityPolicy: false, // Disable CSP }) ); -// All website files are stored in the /views directory. -// 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 -// back here. Which query string converts to what is defined in routes.mjs. +/* All website files are stored in the /views directory. + * 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 + * back here. Which query string converts to what is defined in routes.mjs. + */ router.get('/', async (req, res) => res.send( paintSource( @@ -103,8 +104,8 @@ router.get('/', async (req, res) => path.join( __dirname, 'views', - // Return the error page if the query is not found in - // routes.mjs. Also set index as the default page. + // Return the error page if the query is not found in + // routes.mjs. Also set index as the default page. '/?'.indexOf(req.url) ? pages[Object.keys(req.query)[0]] || 'error.html' : pages.index @@ -125,7 +126,7 @@ app.use('/baremux/', express.static(baremuxPath)); 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) => { res.status(404).send(paintSource(loadTemplates(text404))); }); diff --git a/src/randomization.mjs b/src/randomization.mjs index 89aebe61..99305596 100644 --- a/src/randomization.mjs +++ b/src/randomization.mjs @@ -11,9 +11,10 @@ const { text404, } = pkg; -// Below are lots of function definitions used to obfuscate the website. -// This makes the website harder to properly categorize, as its source code -// changes with each time it is loaded. +/* Below are lots of function definitions used to obfuscate the website. + * This makes the website harder to properly categorize, as its source code + * changes with each time it is loaded. + */ const randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0], charset = /­|​|­|/gi, getRandomChar = randomListItem(charRandom), @@ -28,26 +29,25 @@ const randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0], '', 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) => { for (let item of Object.entries(cacheBustList)) str = str.replaceAll(item[0], item[1]); return str; }, - // Apply the final obfuscation changes to an entire file. + // Apply the final obfuscation changes to an entire file. paintSource = (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), - // 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) => 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) { return lis[Math.floor(Math.random() * lis.length)]; diff --git a/src/server.mjs b/src/server.mjs index 485649b7..2aee29a9 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -27,9 +27,10 @@ const config = Object.freeze( { pages, externalPages } = pageRoutes, __dirname = path.resolve(); -// 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 -// at /ecosystem.config.js. +/* 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 + * at /ecosystem.config.js. + */ const serverUrl = ((base) => { try { base = new URL(config.host); @@ -43,8 +44,8 @@ const serverUrl = ((base) => { })(); console.log(serverUrl); -// 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 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. const shutdown = fileURLToPath(new URL('./.shutdown', import.meta.url)); const rh = createRammerhead(); @@ -85,7 +86,7 @@ const rammerheadSession = /^\/[a-z0-9]{32}/, 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) => { return createServer() .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({ ignoreDuplicateSlashes: true, ignoreTrailingSlash: true, @@ -106,13 +107,13 @@ const app = Fastify({ serverFactory: serverFactory, }); -// Apply Helmet middleware for security +// Apply Helmet middleware for security. app.register(fastifyHelmet, { contentSecurityPolicy: false, // Disable CSP 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, { root: fileURLToPath(new URL('../views/pages', import.meta.url)), decorateReply: false, @@ -133,7 +134,7 @@ app.register(fastifyStatic, { app.register(fastifyStatic, { root: fileURLToPath( 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', import.meta.url ) @@ -145,7 +146,7 @@ app.register(fastifyStatic, { app.register(fastifyStatic, { root: fileURLToPath( 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', import.meta.url ) @@ -154,13 +155,13 @@ app.register(fastifyStatic, { decorateReply: false, }); -// This combines scripts from the official UV repository with local UV scripts into -// one directory path. Local versions of files override the official versions. +// This combines scripts from the official UV repository with local UV scripts into +// one directory path. Local versions of files override the official versions. app.register(fastifyStatic, { root: [ fileURLToPath( 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', import.meta.url ) @@ -171,7 +172,7 @@ app.register(fastifyStatic, { decorateReply: false, }); -// Register proxy paths to the website. +// Register proxy paths to the website. app.register(fastifyStatic, { root: epoxyPath, prefix: '/epoxy/', @@ -196,12 +197,13 @@ app.register(fastifyStatic, { decorateReply: false, }); -// All website files are stored in the /views directory. -// 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 -// back here. Which path converts to what is defined in routes.mjs. +/* All website files are stored in the /views directory. + * 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 + * back here. Which path converts to what is defined in routes.mjs. + */ 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')) console.log(req.raw.rawHeaders[req.raw.rawHeaders.indexOf('Cookie') + 1]); @@ -214,8 +216,8 @@ app.get('/:path', (req, reply) => { return reply.redirect(externalRoute); } - // If a GET request is sent to /test-shutdown and a script-generated shutdown file - // is present, gracefully shut the server down. + // If a GET request is sent to /test-shutdown and a script-generated shutdown file + // is present, gracefully shut the server down. if (reqPath === 'test-shutdown' && existsSync(shutdown)) { console.log('Holy Unblocker is shutting down.'); app.close(); @@ -223,7 +225,7 @@ app.get('/:path', (req, reply) => { 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)) return reply.code(404).type('text/html').send(preloaded404); @@ -234,7 +236,7 @@ app.get('/:path', (req, reply) => { path.join( __dirname, 'views', - // Set the index the as the default page. + // Set the index the as the default page. 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) => { reply.code(404).type('text/html').send(preloaded404); }); diff --git a/src/templates.mjs b/src/templates.mjs index b5137dcb..417c1f70 100644 --- a/src/templates.mjs +++ b/src/templates.mjs @@ -15,11 +15,11 @@ const header = tryReadFile(path.normalize(__dirname + '/header.html')), .replace('', header) .replace('', footer) - // Used only on docs.html + // Used only on docs.html .replace('', documentation) - // Used only on faq.html + // Used only on faq.html .replace('', faq) - // Used only on terms.html + // Used only on terms.html .replace('', terms) - // Used only on header.html + // Used only on header.html .replace('', settings); diff --git a/views/assets/js/card.js b/views/assets/js/card.js index 005ea3e3..48e3228f 100644 --- a/views/assets/js/card.js +++ b/views/assets/js/card.js @@ -4,34 +4,35 @@ /* Card Shimmer Mouse Follow Script /* ----------------------------------------------- */ -// Encase everything in a new scope so that variables are not accidentally -// attached to the global scope. +// Encase everything in a new scope so that variables are not accidentally +// attached to the global scope. (() => { - // Track the cursor position with respect to the top left of the card. - // The "this" keyword gets the element that invoked the event listener. - const handleMouseMove = (element) => { - element.addEventListener('mousemove', (e) => { - const rect = element.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; +// Track the cursor position with respect to the top left of the card. +// The "this" keyword gets the element that invoked the event listener. +const handleMouseMove = (element) => { + element.addEventListener('mousemove', (e) => { + const rect = element.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; - element.style.setProperty('--mouse-x', `${x}px`); - element.style.setProperty('--mouse-y', `${y}px`); - }); - }, - // Reset the cursor tracking variables when the cursor leaves the card. - handleMouseLeave = (element) => { - element.addEventListener('mouseleave', () => { - element.style.setProperty('--mouse-x', `50%`); - element.style.setProperty('--mouse-y', `50%`); - }); - }, - // Get the box card elements and add the event listeners to them. - shimmerEffects = document.querySelectorAll('.box-card, .box-hero'); + element.style.setProperty('--mouse-x', `${x}px`); + element.style.setProperty('--mouse-y', `${y}px`); + }); + }, + // Reset the cursor tracking variables when the cursor leaves the card. + handleMouseLeave = (element) => { + element.addEventListener('mouseleave', () => { + element.style.setProperty('--mouse-x', `50%`); + element.style.setProperty('--mouse-y', `50%`); + }); + }, + // Get the box card elements and add the event listeners to them. + shimmerEffects = document.querySelectorAll('.box-card, .box-hero'); - // Attach CSS variables, mouse-x and mouse-y, to elements that will be - // given shimmer effects, by adding or modifying the style attribute. - // CSS calculates and renders the actual shimmer effect from there. - shimmerEffects.forEach(handleMouseMove); - shimmerEffects.forEach(handleMouseLeave); +/* Attach CSS variables, mouse-x and mouse-y, to elements that will be + * given shimmer effects, by adding or modifying the style attribute. + * CSS calculates and renders the actual shimmer effect from there. + */ +shimmerEffects.forEach(handleMouseMove); +shimmerEffects.forEach(handleMouseLeave); })(); diff --git a/views/assets/js/common-16451543478.js b/views/assets/js/common-16451543478.js index 95303b3e..329903b4 100644 --- a/views/assets/js/common-16451543478.js +++ b/views/assets/js/common-16451543478.js @@ -4,64 +4,65 @@ /* MAIN Holy Unblocker LTS Common Script /* ----------------------------------------------- */ -// Encase everything in a new scope so that variables are not accidentally -// attached to the global scope. +// Encase everything in a new scope so that variables are not accidentally +// attached to the global scope. (() => { - // Get the preferred apex domain name. Not exactly apex, as any - // subdomain other than those listed will be ignored. - const getDomain = () => - location.host.replace(/^(?:www|edu|cooking|beta)\./, ''), - // This is used for stealth mode when visiting external sites. - goFrame = (url) => { - localStorage.setItem('huframesrc', url); - location.href = '/s'; - }, - // Used to set functions for the goProx object at the bottom. - // See the goProx object at the bottom for some usage examples - // on the URL handlers, omnibox functions, and the uvUrl and - // RammerheadEncode functions. - urlHandler = (parser) => - typeof parser === 'function' - ? // Return different functions based on whether a URL has already been set. - // Should help avoid confusion when using or adding to the goProx object. - (url, mode) => { - if (!url) return; - url = parser(url); - mode = `${mode}`.toLowerCase(); - if (mode === 'stealth' || mode == 1) goFrame(url); - else if (mode === 'window' || mode == 0) location.href = url; - else return url; - } - : (mode) => { - mode = `${mode}`.toLowerCase(); - if (mode === 'stealth' || mode == 1) goFrame(parser); - else if (mode === 'window' || mode == 0) location.href = parser; - else return parser; - }, - // An asynchronous version of the function above, just in case. - asyncUrlHandler = (parser) => async (url, mode) => { - if (!url) return; - if (typeof parser === 'function') url = await parser(url); - mode = `${mode}`.toLowerCase(); - if (mode === 'stealth' || mode == 1) goFrame(url); - else if (mode === 'window' || mode == 0) location.href = url; - else return url; - }; - - /* COOKIE AUTH DEMO */ - - const setAuthCookie = (s, lax) => { - document.cookie = - s + - `; expires=${Date.now() + 259200}; SameSite=${lax ? 'Lax' : 'None'}; domain=.${getDomain()}; path=/; Secure;`; +// Get the preferred apex domain name. Not exactly apex, as any +// subdomain other than those listed will be ignored. +const getDomain = () => + location.host.replace(/^(?:www|edu|cooking|beta)\./, ''), + // This is used for stealth mode when visiting external sites. + goFrame = (url) => { + localStorage.setItem('huframesrc', url); + location.href = '/s'; + }, + /* Used to set functions for the goProx object at the bottom. + * See the goProx object at the bottom for some usage examples + * on the URL handlers, omnibox functions, and the uvUrl and + * RammerheadEncode functions. + */ + urlHandler = (parser) => + typeof parser === 'function' + ? // Return different functions based on whether a URL has already been set. + // Should help avoid confusion when using or adding to the goProx object. + (url, mode) => { + if (!url) return; + url = parser(url); + mode = `${mode}`.toLowerCase(); + if (mode === 'stealth' || mode == 1) goFrame(url); + else if (mode === 'window' || mode == 0) location.href = url; + else return url; + } + : (mode) => { + mode = `${mode}`.toLowerCase(); + if (mode === 'stealth' || mode == 1) goFrame(parser); + else if (mode === 'window' || mode == 0) location.href = parser; + else return parser; + }, + // An asynchronous version of the function above, just in case. + asyncUrlHandler = (parser) => async (url, mode) => { + if (!url) return; + if (typeof parser === 'function') url = await parser(url); + mode = `${mode}`.toLowerCase(); + if (mode === 'stealth' || mode == 1) goFrame(url); + else if (mode === 'window' || mode == 0) location.href = url; + else return url; }; - /* OMNIBOX */ +/* COOKIE AUTH DEMO */ - // Search engine is set to Bing. Intended to work just like the usual - // bar at the top of a browser. - const sx = 'bing.com' + '/search?q=', - /* +const setAuthCookie = (s, lax) => { + document.cookie = + s + + `; expires=${Date.now() + 259200}; SameSite=${lax ? 'Lax' : 'None'}; domain=.${getDomain()}; path=/; Secure;`; +}; + +/* OMNIBOX */ + +// Search engine is set to Bing. Intended to work just like the usual +// bar at the top of a browser. +const sx = 'bing.com' + '/search?q=', + /* omnibox = url => (url.indexOf("http") ? "https://" + (url.indexOf(".") < 1 ? sx : "") @@ -69,516 +70,511 @@ + url; */ - // Another omnibox function. Unsure if the version above is needed. - search = (input, template = `https://${sx}%s`) => { - try { - // Return the input if it is already a valid URL. - // eg: https://example.com, https://example.com/test?q=param - return new URL(input) + ''; - } catch (e) { - // Continue if it is invalid. - } - - try { - // Check if the input is valid when http:// is added to the start. - // eg: example.com, https://example.com/test?q=param - const url = new URL(`http://${input}`); - // Return only if the hostname has a TLD or a subdomain. - if (url.hostname.indexOf('.') != -1) return url + ''; - } catch (e) { - // Continue if it is invalid. - } - - // Treat the input as a search query instead of a website. - return template.replace('%s', encodeURIComponent(input)); - }, - // Parse a URL to use with Ultraviolet. - uvUrl = (url) => { - try { - url = - location.origin + - __uv$config.prefix + - __uv$config.encodeUrl(search(url)); - } catch (e) { - // This is for cases where the Ultraviolet scripts have not been loaded. - url = search(url); - } - return url; - }; - - /* RAMMERHEAD CONFIGURATION */ - - // Parse a URL to use with Rammerhead. Only usable if the server is active. - const RammerheadEncode = async (baseUrl) => { - // Hellhead - const mod = (n, m) => ((n % m) + m) % m, - baseDictionary = - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-', - shuffledIndicator = '_rhs', - // Return a copy of the base dictionary with a randomized character order. - // Will be used as a Caesar cipher for URL encoding. - generateDictionary = () => { - let str = ''; - const split = baseDictionary.split(''); - while (split.length > 0) { - // Using .splice automatically rounds down to the nearest whole number. - str += split.splice(Math.random() * split.length, 1)[0]; - } - return str; - }; - - class StrShuffler { - constructor(dictionary = generateDictionary()) { - this.dictionary = dictionary; - } - - shuffle(str) { - // Do not reshuffle an already shuffled string. - if (!str.indexOf(shuffledIndicator)) return str; - - let shuffledStr = ''; - for (let i = 0; i < str.length; i++) { - const char = str[i], - idx = baseDictionary.indexOf(char); - - // For URL encoded characters and characters not included in the - // dictionary, leave untouched. Otherwise, replace with a character - // from the dictionary. - if (char === '%' && str.length - i >= 3) - // A % symbol denotes that the next 2 characters are URL encoded. - shuffledStr += char + str[++i] + str[++i]; - // Do not modify unrecognized characters. - else if (idx == -1) shuffledStr += char; - // Find the corresponding dictionary entry and use the character - // that is i places to the right of it. - else - shuffledStr += this.dictionary[mod(idx + i, baseDictionary.length)]; - } - // Add a prefix signifying that the string has been shuffled. - return shuffledIndicator + shuffledStr; - } - - // Unshuffling is currently not done on the client side, and likely - // won't ever be for this implementation. It is used by the server instead. - unshuffle(str) { - // Do not unshuffle an already unshuffled string. - if (str.indexOf(shuffledIndicator)) return str; - - // Remove the prefix signifying that the string has been shuffled. - str = str.slice(shuffledIndicator.length); - - let unshuffledStr = ''; - for (let i = 0; i < str.length; i++) { - const char = str[i], - idx = this.dictionary.indexOf(char); - - // Convert the dictionary entry characters back into their base - // characters using the base dictionary. Again, leave URL encoded - // characters and unrecognized symbols alone. - if (char === '%' && str.length - i >= 3) - unshuffledStr += char + str[++i] + str[++i]; - else if (idx == -1) unshuffledStr += char; - // Find the corresponding base character entry and use the character - // that is i places to the left of it. - else - unshuffledStr += - baseDictionary[mod(idx - i, baseDictionary.length)]; - } - return unshuffledStr; - } + // Another omnibox function. Unsure if the version above is needed. + search = (input, template = `https://${sx}%s`) => { + try { + // Return the input if it is already a valid URL. + // eg: https://example.com, https://example.com/test?q=param + return new URL(input) + ''; + } catch (e) { + // Continue if it is invalid. } - // Request information that's beiing stored elsewhere on the server. - // Executes the callback function if the server responds as intended. - const get = (url, callback, shush = false) => { - let request = new XMLHttpRequest(); - request.open('GET', url, true); - request.send(); + try { + // Check if the input is valid when http:// is added to the start. + // eg: example.com, https://example.com/test?q=param + const url = new URL(`http://${input}`); + // Return only if the hostname has a TLD or a subdomain. + if (url.hostname.indexOf('.') != -1) return url + ''; + } catch (e) { + // Continue if it is invalid. + } - request.onerror = () => { - if (!shush) console.log('Cannot communicate with the server'); - }; - request.onload = () => { - if (request.status === 200) callback(request.responseText); - else if (!shush) - console.log( - `Unexpected server response to not match "200". Server says "${request.responseText}"` - ); - }; - }, - // Functions for interacting with Rammerhead backend code on the server. - api = { - // Make a new Rammerhead session and do something with it. - newsession(callback) { - get('/newsession', callback); - }, - - // Check if a session with the specified ID exists, then do something. - sessionexists(id, callback) { - get('/sessionexists?id=' + encodeURIComponent(id), (res) => { - if (res === 'exists') return callback(true); - if (res === 'not found') return callback(false); - console.log('Unexpected response from server. Received ' + res); - }); - }, - - // Request a brand new encoding table to use for Rammerhead. - shuffleDict(id, callback) { - console.log('Shuffling', id); - get('/api/shuffleDict?id=' + encodeURIComponent(id), (res) => { - callback(JSON.parse(res)); - }); - }, - }, - // Organize Rammerhead sessions via the browser's local storage. - // Local data consists of session creation timestamps and session IDs. - // The rest of the data is stored on the server. - localStorageKey = 'rammerhead_sessionids', - localStorageKeyDefault = 'rammerhead_default_sessionid', - sessionIdsStore = { - // Get the local data of all stored sessions. - get() { - const rawData = localStorage.getItem(localStorageKey); - if (!rawData) return []; - try { - const data = JSON.parse(rawData); - - // Catch invalidly stored Rammerhead session data. Either that or - // it's poorly spoofed. - if (!Array.isArray(data)) throw 'getout'; - return data; - } catch (e) { - return []; - } - }, - - // Store local Rammerhead session data in the form of an array. - set(data) { - if (!Array.isArray(data)) throw new TypeError('Must be an array.'); - localStorage.setItem(localStorageKey, JSON.stringify(data)); - }, - - // Get the default session data. - getDefault() { - const sessionId = localStorage.getItem(localStorageKeyDefault); - if (sessionId) { - let data = sessionIdsStore.get(); - data.filter((session) => session.id === sessionId); - if (data.length) return data[0]; - } - return null; - }, - - // Set a new default session based on a given session ID. - setDefault(id) { - localStorage.setItem(localStorageKeyDefault, id); - }, - }, - // Store or update local data for a Rammerhead session, which consists of - // the session's ID and when the session was last created. - addSession = (id) => { - let data = sessionIdsStore.get(); - data.unshift({ id: id, createdOn: new Date().toLocaleString() }); - sessionIdsStore.set(data); - }, - // Attempt to load an existing session that has been stored on the server. - getSessionId = () => { - return new Promise((resolve) => { - // Check if the browser has stored an existing session. - const id = localStorage.getItem('session-string'); - api.sessionexists(id, (value) => { - // Create a new session if Rammerhead can't find an existing session. - if (!value) { - console.log('Session validation failed'); - api.newsession((id) => { - addSession(id); - localStorage.setItem('session-string', id); - console.log(id); - console.log('^ new id'); - resolve(id); - }); - } - // Load the stored session now that Rammerhead has found it. - else resolve(id); - }); - }); - }; - - // Load the URL that was last visited in the Rammerhead session. - return getSessionId().then( - (id) => - new Promise((resolve) => { - api.shuffleDict(id, (shuffleDict) => { - // Encode the URL with Rammerhead's encoding table and return the URL. - resolve(`/${id}/` + new StrShuffler(shuffleDict).shuffle(baseUrl)); - }); - }) - ); + // Treat the input as a search query instead of a website. + return template.replace('%s', encodeURIComponent(input)); + }, + // Parse a URL to use with Ultraviolet. + uvUrl = (url) => { + try { + url = + location.origin + + __uv$config.prefix + + __uv$config.encodeUrl(search(url)); + } catch (e) { + // This is for cases where the Ultraviolet scripts have not been loaded. + url = search(url); + } + return url; }; - /* To use: - * goProx.proxy(url-string, mode-as-string-or-number); - * - * Key: 1 = "stealth" - * 0 = "window" - * Nothing = return URL as a string - * - * Examples: - * Stealth mode - - * goProx.ultraviolet("https://google.com", 1); - * goProx.ultraviolet("https://google.com", "stealth"); - * - * await goProx.rammerhead("https://google.com", 1); - * await goProx.rammerhead("https://google.com", "stealth"); - * - * goProx.searx(1); - * goProx.searx("stealth"); - * - * Window mode - - * goProx.ultraviolet("https://google.com", "window"); - * - * await goProx.rammerhead("https://google.com", "window"); - * - * goProx.searx("window"); - * - * Return string value mode (default) - - * goProx.ultraviolet("https://google.com"); - * - * await goProx.rammerhead("https://google.com"); - * - * goProx.searx(); - */ - addEventListener('DOMContentLoaded', async () => { - const goProx = { - // `location.protocol + "//" + getDomain()` more like `location.origin` - // setAuthCookie("__cor_auth=1", false); - ultraviolet: urlHandler(uvUrl), +/* RAMMERHEAD CONFIGURATION */ - rammerhead: asyncUrlHandler( - async (url) => location.origin + (await RammerheadEncode(search(url))) - ), - - searx: urlHandler(location.protocol + `//c.${getDomain()}/engine/`), - - libreddit: urlHandler(location.protocol + '//c.' + getDomain()), - - rnav: urlHandler(location.protocol + '//client.' + getDomain()), - - osu: urlHandler(location.origin + '/archive/osu'), - - mcnow: urlHandler(uvUrl('https://now.gg/play/a/10010/b')), - - glife: urlHandler( - uvUrl('https://now.gg/apps/lunime/5767/gacha-life.html') - ), - - roblox: urlHandler( - uvUrl('https://now.gg/apps/roblox-corporation/5349/roblox.html') - ), - - amongus: urlHandler( - uvUrl('https://now.gg/apps/innersloth-llc/4047/among-us.html') - ), - - pubg: urlHandler( - uvUrl( - 'https://now.gg/apps/proxima-beta/2609/pubg-mobile-resistance.html' - ) - ), - - train: urlHandler(uvUrl('https://hby.itch.io/last-train-home')), - - village: urlHandler(uvUrl('https://kwoodhouse.itch.io/village-arsonist')), - - prison: urlHandler(uvUrl('https://vimlark.itch.io/pick-up-prison')), - - rpg: urlHandler(uvUrl('https://alarts.itch.io/die-in-the-dungeon')), - - speed: urlHandler( - uvUrl('https://captain4lk.itch.io/what-the-road-brings') - ), - - heli: urlHandler(uvUrl('https://benjames171.itch.io/helo-storm')), - - youtube: urlHandler(uvUrl('https://youtube.com')), - - discordUV: urlHandler(uvUrl('https://discord.com/app')), +// Parse a URL to use with Rammerhead. Only usable if the server is active. +const RammerheadEncode = async (baseUrl) => { + // Hellhead + const mod = (n, m) => ((n % m) + m) % m, + baseDictionary = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-', + shuffledIndicator = '_rhs', + // Return a copy of the base dictionary with a randomized character order. + // Will be used as a Caesar cipher for URL encoding. + generateDictionary = () => { + let str = ''; + const split = baseDictionary.split(''); + while (split.length > 0) { + // Using .splice automatically rounds down to the nearest whole number. + str += split.splice(Math.random() * split.length, 1)[0]; + } + return str; }; - // Don't slow down the rest of the script while encoding the URL. - RammerheadEncode('https://discord.com/app').then((url) => { - goProx.discordRH = urlHandler(url); + class StrShuffler { + constructor(dictionary = generateDictionary()) { + this.dictionary = dictionary; + } - // Object.freeze prevents goProx from accidentally being edited. - Object.freeze(goProx); - }); + shuffle(str) { + // Do not reshuffle an already shuffled string. + if (!str.indexOf(shuffledIndicator)) return str; - // Attach event listeners using goProx to specific app menus that need it. - const prSet = (id, type) => { - const formElement = document.getElementById(id); - if (!formElement) return; + let shuffledStr = ''; + for (let i = 0; i < str.length; i++) { + const char = str[i], + idx = baseDictionary.indexOf(char); - let prUrl = formElement.querySelector('input[type=text]'), - prGo1 = document.querySelectorAll(`#${id}.pr-go1, #${id} .pr-go1`), - prGo2 = document.querySelectorAll(`#${id}.pr-go2, #${id} .pr-go2`); + /* For URL encoded characters and characters not included in the + * dictionary, leave untouched. Otherwise, replace with a character + * from the dictionary. + */ + if (char === '%' && str.length - i >= 3) + // A % symbol denotes that the next 2 characters are URL encoded. + shuffledStr += char + str[++i] + str[++i]; + // Do not modify unrecognized characters. + else if (idx == -1) shuffledStr += char; + // Find the corresponding dictionary entry and use the character + // that is i places to the right of it. + else + shuffledStr += this.dictionary[mod(idx + i, baseDictionary.length)]; + } + // Add a prefix signifying that the string has been shuffled. + return shuffledIndicator + shuffledStr; + } - // Handle the other menu buttons differently if there is no omnibox. Menus - // which lack an omnibox likely use buttons as mere links. - const goProxMethod = prUrl - ? (mode) => () => { - goProx[type](prUrl.value, mode); - } - : (mode) => () => { - goProx[type](mode); - }, - // Ultraviolet is currently incompatible with window mode. - searchMode = type === 'ultraviolet' ? 'stealth' : 'window'; + // Unshuffling is currently not done on the client side, and likely + // won't ever be for this implementation. It is used by the server instead. + unshuffle(str) { + // Do not unshuffle an already unshuffled string. + if (str.indexOf(shuffledIndicator)) return str; - if (prUrl) - prUrl.addEventListener('keydown', async (e) => { - if (e.code === 'Enter') goProxMethod(searchMode)(); - // This is exclusively used for the validator script. - else if (e.code === 'Validator Test') { - e.target.value = await goProx[type](e.target.value); - e.target.dispatchEvent(new Event('change')); - } + // Remove the prefix signifying that the string has been shuffled. + str = str.slice(shuffledIndicator.length); + + let unshuffledStr = ''; + for (let i = 0; i < str.length; i++) { + const char = str[i], + idx = this.dictionary.indexOf(char); + + /* Convert the dictionary entry characters back into their base + * characters using the base dictionary. Again, leave URL encoded + * characters and unrecognized symbols alone. + */ + if (char === '%' && str.length - i >= 3) + unshuffledStr += char + str[++i] + str[++i]; + else if (idx == -1) unshuffledStr += char; + // Find the corresponding base character entry and use the character + // that is i places to the left of it. + else + unshuffledStr += baseDictionary[mod(idx - i, baseDictionary.length)]; + } + return unshuffledStr; + } + } + + // Request information that's beiing stored elsewhere on the server. + // Executes the callback function if the server responds as intended. + const get = (url, callback, shush = false) => { + let request = new XMLHttpRequest(); + request.open('GET', url, true); + request.send(); + + request.onerror = () => { + if (!shush) console.log('Cannot communicate with the server'); + }; + request.onload = () => { + if (request.status === 200) callback(request.responseText); + else if (!shush) + console.log( + `Unexpected server response to not match "200". Server says "${request.responseText}"` + ); + }; + }, + // Functions for interacting with Rammerhead backend code on the server. + api = { + // Make a new Rammerhead session and do something with it. + newsession(callback) { + get('/newsession', callback); + }, + + // Check if a session with the specified ID exists, then do something. + sessionexists(id, callback) { + get('/sessionexists?id=' + encodeURIComponent(id), (res) => { + if (res === 'exists') return callback(true); + if (res === 'not found') return callback(false); + console.log('Unexpected response from server. Received ' + res); }); + }, - prGo1.forEach((element) => { - element.addEventListener('click', goProxMethod('window')); - }); - prGo2.forEach((element) => { - element.addEventListener('click', goProxMethod('stealth')); - }); - }; + // Request a brand new encoding table to use for Rammerhead. + shuffleDict(id, callback) { + console.log('Shuffling', id); + get('/api/shuffleDict?id=' + encodeURIComponent(id), (res) => { + callback(JSON.parse(res)); + }); + }, + }, + /* Organize Rammerhead sessions via the browser's local storage. + * Local data consists of session creation timestamps and session IDs. + * The rest of the data is stored on the server. + */ + localStorageKey = 'rammerhead_sessionids', + localStorageKeyDefault = 'rammerhead_default_sessionid', + sessionIdsStore = { + // Get the local data of all stored sessions. + get() { + const rawData = localStorage.getItem(localStorageKey); + if (!rawData) return []; + try { + const data = JSON.parse(rawData); - prSet('pr-uv', 'ultraviolet'); - prSet('pr-rh', 'rammerhead'); - prSet('pr-yt', 'youtube'); - prSet('pr-rh-dc', 'discordRH'); - prSet('pr-uv-dc', 'discordUV'); - - // Load in relevant JSON files used to organize large sets of data. - // This first one is for links, whereas the rest are for navigation menus. - const huLinks = await fetch('/assets/json/links.json', { - mode: 'same-origin', - }).then((response) => response.json()); - - for (let items = Object.entries(huLinks), i = 0; i < items.length; i++) - // Replace all placeholder links with the corresponding entry in huLinks. - (document.getElementById(items[i][0]) || {}).href = items[i][1]; - - const navLists = { - // Pair an element ID with a JSON file name. They are identical for now. - 'emu-nav': 'emu-nav', - 'emulib-nav': 'emulib-nav', - 'flash-nav': 'flash-nav', - 'h5-nav': 'h5-nav', - }; - - for (const [listId, filename] of Object.entries(navLists)) { - let navList = document.getElementById(listId); - - if (navList) { - // List items stored in JSON format will be returned as a JS object. - const data = await fetch(`/assets/json/${filename}.json`, { - mode: 'same-origin', - }).then((response) => response.json()); - - // Load the JSON lists into specific HTML parent elements as groups of - // child elements, if the parent element is found. - switch (filename) { - case 'emu-nav': - case 'emulib-nav': - case 'h5-nav': { - const dirnames = { - // Set the directory of where each item of the corresponding JSON - // list will be retrieved from. - 'emu-nav': 'emu', - 'emulib-nav': 'emulib', - 'h5-nav': 'h5g', - }, - dir = dirnames[filename], - // Add a little functionality for each list item when clicked on. - clickHandler = (parser, a) => (e) => { - if (e.target == a || e.target.tagName != 'A') { - e.preventDefault(); - parser(item); - } - }; - - for (let i = 0; i < data.length; i++) { - // Load each item as an anchor tag with an image, heading, - // description, and click event listener. - const item = data[i], - a = document.createElement('a'), - img = document.createElement('img'), - title = document.createElement('h3'), - desc = document.createElement('p'); - - a.href = '#'; - img.src = `/assets/img/${dir}/` + item.img; - title.textContent = item.name; - desc.textContent = item.description; - - if (filename === 'h5-nav') { - if (item.credits === 'itch') - desc.innerHTML += - '
Credits: Game can be found here.'; - if (item.credits === 'nowgg') - desc.innerHTML += - '
Credits: Game can be found here.'; - } - - a.appendChild(img); - a.appendChild(title); - a.appendChild(desc); - - // Which function is used for the click event is determined by - // the corresponding location/index in the dirnames object. - const functionsList = [ - () => goFrame(item.path), - () => - goFrame('/webretro?core=' + item.core + '&rom=' + item.rom), - item.custom - ? () => goProx[item.custom]('stealth') - : () => goFrame('/archive/g/' + item.path), - ]; - - a.addEventListener( - 'click', - clickHandler( - functionsList[Object.values(dirnames).indexOf(dir)], - a - ) - ); - - navList.appendChild(a); - } - break; - } - - case 'flash-nav': - for (let i = 0; i < data.length; i++) { - // Load each item as an anchor tag with a short title and click - // event listener. - const item = data[i], - a = document.createElement('a'); - a.href = '#'; - a.textContent = item.slice(0, -4); - - a.addEventListener('click', (e) => { - e.preventDefault(); - goFrame('/flash?swf=' + item); - }); - - navList.appendChild(a); - } - break; - - // No default case. + // Catch invalidly stored Rammerhead session data. Either that or + // it's poorly spoofed. + if (!Array.isArray(data)) throw 'getout'; + return data; + } catch (e) { + return []; } + }, + + // Store local Rammerhead session data in the form of an array. + set(data) { + if (!Array.isArray(data)) throw new TypeError('Must be an array.'); + localStorage.setItem(localStorageKey, JSON.stringify(data)); + }, + + // Get the default session data. + getDefault() { + const sessionId = localStorage.getItem(localStorageKeyDefault); + if (sessionId) { + let data = sessionIdsStore.get(); + data.filter((session) => session.id === sessionId); + if (data.length) return data[0]; + } + return null; + }, + + // Set a new default session based on a given session ID. + setDefault(id) { + localStorage.setItem(localStorageKeyDefault, id); + }, + }, + // Store or update local data for a Rammerhead session, which consists of + // the session's ID and when the session was last created. + addSession = (id) => { + let data = sessionIdsStore.get(); + data.unshift({ id: id, createdOn: new Date().toLocaleString() }); + sessionIdsStore.set(data); + }, + // Attempt to load an existing session that has been stored on the server. + getSessionId = () => { + return new Promise((resolve) => { + // Check if the browser has stored an existing session. + const id = localStorage.getItem('session-string'); + api.sessionexists(id, (value) => { + // Create a new session if Rammerhead can't find an existing session. + if (!value) { + console.log('Session validation failed'); + api.newsession((id) => { + addSession(id); + localStorage.setItem('session-string', id); + console.log(id); + console.log('^ new id'); + resolve(id); + }); + } + // Load the stored session now that Rammerhead has found it. + else resolve(id); + }); + }); + }; + + // Load the URL that was last visited in the Rammerhead session. + return getSessionId().then( + (id) => + new Promise((resolve) => { + api.shuffleDict(id, (shuffleDict) => { + // Encode the URL with Rammerhead's encoding table and return the URL. + resolve(`/${id}/` + new StrShuffler(shuffleDict).shuffle(baseUrl)); + }); + }) + ); +}; + +/* To use: + * goProx.proxy(url-string, mode-as-string-or-number); + * + * Key: 1 = "stealth" + * 0 = "window" + * Nothing = return URL as a string + * + * Examples: + * Stealth mode - + * goProx.ultraviolet("https://google.com", 1); + * goProx.ultraviolet("https://google.com", "stealth"); + * + * await goProx.rammerhead("https://google.com", 1); + * await goProx.rammerhead("https://google.com", "stealth"); + * + * goProx.searx(1); + * goProx.searx("stealth"); + * + * Window mode - + * goProx.ultraviolet("https://google.com", "window"); + * + * await goProx.rammerhead("https://google.com", "window"); + * + * goProx.searx("window"); + * + * Return string value mode (default) - + * goProx.ultraviolet("https://google.com"); + * + * await goProx.rammerhead("https://google.com"); + * + * goProx.searx(); + */ +addEventListener('DOMContentLoaded', async () => { + const goProx = { + // `location.protocol + "//" + getDomain()` more like `location.origin` + // setAuthCookie("__cor_auth=1", false); + ultraviolet: urlHandler(uvUrl), + + rammerhead: asyncUrlHandler( + async (url) => location.origin + (await RammerheadEncode(search(url))) + ), + + searx: urlHandler(location.protocol + `//c.${getDomain()}/engine/`), + + libreddit: urlHandler(location.protocol + '//c.' + getDomain()), + + rnav: urlHandler(location.protocol + '//client.' + getDomain()), + + osu: urlHandler(location.origin + '/archive/osu'), + + mcnow: urlHandler(uvUrl('https://now.gg/play/a/10010/b')), + + glife: urlHandler(uvUrl('https://now.gg/apps/lunime/5767/gacha-life.html')), + + roblox: urlHandler( + uvUrl('https://now.gg/apps/roblox-corporation/5349/roblox.html') + ), + + amongus: urlHandler( + uvUrl('https://now.gg/apps/innersloth-llc/4047/among-us.html') + ), + + pubg: urlHandler( + uvUrl('https://now.gg/apps/proxima-beta/2609/pubg-mobile-resistance.html') + ), + + train: urlHandler(uvUrl('https://hby.itch.io/last-train-home')), + + village: urlHandler(uvUrl('https://kwoodhouse.itch.io/village-arsonist')), + + prison: urlHandler(uvUrl('https://vimlark.itch.io/pick-up-prison')), + + rpg: urlHandler(uvUrl('https://alarts.itch.io/die-in-the-dungeon')), + + speed: urlHandler(uvUrl('https://captain4lk.itch.io/what-the-road-brings')), + + heli: urlHandler(uvUrl('https://benjames171.itch.io/helo-storm')), + + youtube: urlHandler(uvUrl('https://youtube.com')), + + discordUV: urlHandler(uvUrl('https://discord.com/app')), + }; + + // Don't slow down the rest of the script while encoding the URL. + RammerheadEncode('https://discord.com/app').then((url) => { + goProx.discordRH = urlHandler(url); + + // Object.freeze prevents goProx from accidentally being edited. + Object.freeze(goProx); + }); + + // Attach event listeners using goProx to specific app menus that need it. + const prSet = (id, type) => { + const formElement = document.getElementById(id); + if (!formElement) return; + + let prUrl = formElement.querySelector('input[type=text]'), + prGo1 = document.querySelectorAll(`#${id}.pr-go1, #${id} .pr-go1`), + prGo2 = document.querySelectorAll(`#${id}.pr-go2, #${id} .pr-go2`); + + // Handle the other menu buttons differently if there is no omnibox. Menus + // which lack an omnibox likely use buttons as mere links. + const goProxMethod = prUrl + ? (mode) => () => { + goProx[type](prUrl.value, mode); + } + : (mode) => () => { + goProx[type](mode); + }, + // Ultraviolet is currently incompatible with window mode. + searchMode = type === 'ultraviolet' ? 'stealth' : 'window'; + + if (prUrl) + prUrl.addEventListener('keydown', async (e) => { + if (e.code === 'Enter') goProxMethod(searchMode)(); + // This is exclusively used for the validator script. + else if (e.code === 'Validator Test') { + e.target.value = await goProx[type](e.target.value); + e.target.dispatchEvent(new Event('change')); + } + }); + + prGo1.forEach((element) => { + element.addEventListener('click', goProxMethod('window')); + }); + prGo2.forEach((element) => { + element.addEventListener('click', goProxMethod('stealth')); + }); + }; + + prSet('pr-uv', 'ultraviolet'); + prSet('pr-rh', 'rammerhead'); + prSet('pr-yt', 'youtube'); + prSet('pr-rh-dc', 'discordRH'); + prSet('pr-uv-dc', 'discordUV'); + + // Load in relevant JSON files used to organize large sets of data. + // This first one is for links, whereas the rest are for navigation menus. + const huLinks = await fetch('/assets/json/links.json', { + mode: 'same-origin', + }).then((response) => response.json()); + + for (let items = Object.entries(huLinks), i = 0; i < items.length; i++) + // Replace all placeholder links with the corresponding entry in huLinks. + (document.getElementById(items[i][0]) || {}).href = items[i][1]; + + const navLists = { + // Pair an element ID with a JSON file name. They are identical for now. + 'emu-nav': 'emu-nav', + 'emulib-nav': 'emulib-nav', + 'flash-nav': 'flash-nav', + 'h5-nav': 'h5-nav', + }; + + for (const [listId, filename] of Object.entries(navLists)) { + let navList = document.getElementById(listId); + + if (navList) { + // List items stored in JSON format will be returned as a JS object. + const data = await fetch(`/assets/json/${filename}.json`, { + mode: 'same-origin', + }).then((response) => response.json()); + + // Load the JSON lists into specific HTML parent elements as groups of + // child elements, if the parent element is found. + switch (filename) { + case 'emu-nav': + case 'emulib-nav': + case 'h5-nav': { + const dirnames = { + // Set the directory of where each item of the corresponding JSON + // list will be retrieved from. + 'emu-nav': 'emu', + 'emulib-nav': 'emulib', + 'h5-nav': 'h5g', + }, + dir = dirnames[filename], + // Add a little functionality for each list item when clicked on. + clickHandler = (parser, a) => (e) => { + if (e.target == a || e.target.tagName != 'A') { + e.preventDefault(); + parser(item); + } + }; + + for (let i = 0; i < data.length; i++) { + // Load each item as an anchor tag with an image, heading, + // description, and click event listener. + const item = data[i], + a = document.createElement('a'), + img = document.createElement('img'), + title = document.createElement('h3'), + desc = document.createElement('p'); + + a.href = '#'; + img.src = `/assets/img/${dir}/` + item.img; + title.textContent = item.name; + desc.textContent = item.description; + + if (filename === 'h5-nav') { + if (item.credits === 'itch') + desc.innerHTML += + '
Credits: Game can be found here.'; + if (item.credits === 'nowgg') + desc.innerHTML += + '
Credits: Game can be found here.'; + } + + a.appendChild(img); + a.appendChild(title); + a.appendChild(desc); + + // Which function is used for the click event is determined by + // the corresponding location/index in the dirnames object. + const functionsList = [ + () => goFrame(item.path), + () => goFrame('/webretro?core=' + item.core + '&rom=' + item.rom), + item.custom + ? () => goProx[item.custom]('stealth') + : () => goFrame('/archive/g/' + item.path), + ]; + + a.addEventListener( + 'click', + clickHandler( + functionsList[Object.values(dirnames).indexOf(dir)], + a + ) + ); + + navList.appendChild(a); + } + break; + } + + case 'flash-nav': + for (let i = 0; i < data.length; i++) { + // Load each item as an anchor tag with a short title and click + // event listener. + const item = data[i], + a = document.createElement('a'); + a.href = '#'; + a.textContent = item.slice(0, -4); + + a.addEventListener('click', (e) => { + e.preventDefault(); + goFrame('/flash?swf=' + item); + }); + + navList.appendChild(a); + } + break; + + // No default case. } } - }); + } +}); })(); diff --git a/views/assets/js/csel.js b/views/assets/js/csel.js index 5731ca43..5f5982ca 100644 --- a/views/assets/js/csel.js +++ b/views/assets/js/csel.js @@ -5,173 +5,168 @@ /* Settings Menu /* ----------------------------------------------- */ -// Encase everything in a new scope so that variables are not accidentally -// attached to the global scope. +// Encase everything in a new scope so that variables are not accidentally +// attached to the global scope. (() => { - // Determine the expiration date of a new cookie. - let date = new Date(); - date.setFullYear(date.getFullYear() + 100); - date = date.toUTCString(); +// Determine the expiration date of a new cookie. +let date = new Date(); +date.setFullYear(date.getFullYear() + 100); +date = date.toUTCString(); - // All cookies should be secure and are intended to work in iframes. - const setCookie = (name, value) => { - document.cookie = - name + - `=${encodeURIComponent(value)}; expires=${date}; SameSite=None; Secure;`; - }, - removeCookie = (name) => { - document.cookie = - name + - '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure;'; - }, - readCookie = async (name) => { - // Get the first cookie that has the same name. - for (let cookie of document.cookie.split('; ')) - if (!cookie.indexOf(name + '=')) - // Return the cookie's stored content. - return decodeURIComponent(cookie.slice(name.length + 1)); - }, - // Customize the page's title. - pageTitle = (value) => { - let tag = - document.getElementsByTagName('title')[0] || - document.createElement('title'); - tag.innerHTML = value; - document.head.appendChild(tag); - }, - // Set the page's favicon to a new URL. - pageIcon = (value) => { - let tag = - document.querySelector("link[rel*='icon']") || - document.createElement('link'); - tag.rel = 'icon'; - tag.href = value; - document.head.appendChild(tag); - }, - // Make a small stylesheet to override a setting from the main stylesheet. - pageShowAds = () => { - let advertising = document.createElement('style'); - advertising.id = 'advertising'; - advertising.innerText = '.ad { display:block; }'; +// All cookies should be secure and are intended to work in iframes. +const setCookie = (name, value) => { + document.cookie = + name + + `=${encodeURIComponent(value)}; expires=${date}; SameSite=None; Secure;`; + }, + removeCookie = (name) => { + document.cookie = + name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=None; Secure;'; + }, + readCookie = async (name) => { + // Get the first cookie that has the same name. + for (let cookie of document.cookie.split('; ')) + if (!cookie.indexOf(name + '=')) + // Return the cookie's stored content. + return decodeURIComponent(cookie.slice(name.length + 1)); + }, + // Customize the page's title. + pageTitle = (value) => { + let tag = + document.getElementsByTagName('title')[0] || + document.createElement('title'); + tag.innerHTML = value; + document.head.appendChild(tag); + }, + // Set the page's favicon to a new URL. + pageIcon = (value) => { + let tag = + document.querySelector("link[rel*='icon']") || + document.createElement('link'); + tag.rel = 'icon'; + tag.href = value; + document.head.appendChild(tag); + }, + // Make a small stylesheet to override a setting from the main stylesheet. + pageShowAds = () => { + let advertising = document.createElement('style'); + advertising.id = 'advertising'; + 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.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'; + document.getElementById(selector) || document.querySelector(selector) + ).addEventListener(...args), + focusElement = document + .getElementsByClassName('dropdown-settings')[0] + .parentElement.querySelector("a[href='#']"); - // 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); + attachEventListener('.dropdown-settings .close-settings-btn', 'click', () => { + document.activeElement.blur(); }); - // 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')); + // 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'); } }); - // All code below is used by the Settings UI in the navigation bar. - if (document.getElementById('csel')) { - const attachEventListener = (selector, ...args) => - ( - document.getElementById(selector) || document.querySelector(selector) - ).addEventListener(...args), - focusElement = document - .getElementsByClassName('dropdown-settings')[0] - .parentElement.querySelector("a[href='#']"); + // 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'); + } + }); - 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. -// 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. + // 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. attachEventListener("cselab", "click", () => { let win = window.open(); let iframe = win.document.createElement("iframe"); @@ -179,70 +174,71 @@ iframe.src = location.href; win.document.body.appendChild(iframe); }); -*/ + */ - // Provides users with a handy set of title and icon autofill options. - attachEventListener('icon-list', 'change', (e) => { - let titleform = document.getElementById('titleform'), - iconform = document.getElementById('iconform'); - [titleform.firstElementChild.value, iconform.firstElementChild.value] = ( - presetIcons[e.target.value] || ' \n ' - ).split(' \n '); + // Provides users with a handy set of title and icon autofill options. + attachEventListener('icon-list', 'change', (e) => { + let titleform = document.getElementById('titleform'), + iconform = document.getElementById('iconform'); + [titleform.firstElementChild.value, iconform.firstElementChild.value] = ( + presetIcons[e.target.value] || ' \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. - 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); + // Allow users to toggle ads with the UI. + attachEventListener('hideads', 'change', (e) => { + if (e.target.checked) { + pageHideAds(); + setCookie('HBHideAds', 'true'); + } else { + pageShowAds(); + setCookie('HBHideAds', 'false'); + } + }); - // 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 toggle ads with the UI. - attachEventListener('hideads', 'change', (e) => { - if (e.target.checked) { - pageHideAds(); - setCookie('HBHideAds', 'true'); - } 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])' + /* 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) { + let selectedMode = document.querySelector( + '#uv-transport-list input[value=libcurl]' ); - if (e.target.checked) { - let selectedMode = document.querySelector( - '#uv-transport-list input[value=libcurl]' - ); - unselectedModes.forEach((e) => { - e.setAttribute('disabled', 'true'); - }); - selectedMode.click(); - setCookie('HBUseOnion', 'true'); - } else { - unselectedModes.forEach((e) => { - e.removeAttribute('disabled'); - }); + unselectedModes.forEach((e) => { + e.setAttribute('disabled', 'true'); + }); + selectedMode.click(); + setCookie('HBUseOnion', 'true'); + } else { + unselectedModes.forEach((e) => { + e.removeAttribute('disabled'); + }); - // Tor will likely never be enabled by default, so removing the cookie - // here may be better than setting it to false. - removeCookie('HBUseOnion'); - } - }); - } + // Tor will likely never be enabled by default, so removing the cookie + // here may be better than setting it to false. + removeCookie('HBUseOnion'); + } + }); +} })(); /* ----------------------------------------------- diff --git a/views/assets/js/register-sw.js b/views/assets/js/register-sw.js index d8db463c..851ec570 100644 --- a/views/assets/js/register-sw.js +++ b/views/assets/js/register-sw.js @@ -1,83 +1,84 @@ -// Encase everything in a new scope so that variables are not accidentally -// attached to the global scope. +// Encase everything in a new scope so that variables are not accidentally +// attached to the global scope. (() => { - const stockSW = '/uv/sw.js', - blacklistSW = '/uv/sw-blacklist.js', - swAllowedHostnames = ['localhost', '127.0.0.1'], - connection = new BareMux.BareMuxConnection('/baremux/worker.js'), - wispUrl = - (location.protocol === 'https:' ? 'wss' : 'ws') + - '://' + - location.host + - '/wisp/', - // Proxy configuration - proxyUrl = 'socks5h://localhost:9050', // Replace with your proxy URL - transports = { - epoxy: '/epoxy/index.mjs', - libcurl: '/libcurl/index.mjs', - bare: '/baremux/index.mjs', - }, - // The following two variables are copied and pasted here from csel.js. - readCookie = async (name) => { - // Get the first cookie that has the same name. - for (let cookie of document.cookie.split('; ')) - if (!cookie.indexOf(name + '=')) - // Return the cookie's stored content. - return decodeURIComponent(cookie.slice(name.length + 1)); - }, - // Sets the default transport mode 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'; +const stockSW = '/uv/sw.js', + blacklistSW = '/uv/sw-blacklist.js', + swAllowedHostnames = ['localhost', '127.0.0.1'], + connection = new BareMux.BareMuxConnection('/baremux/worker.js'), + wispUrl = + (location.protocol === 'https:' ? 'wss' : 'ws') + + '://' + + location.host + + '/wisp/', + // Proxy configuration + proxyUrl = 'socks5h://localhost:9050', // Replace with your proxy URL + transports = { + epoxy: '/epoxy/index.mjs', + libcurl: '/libcurl/index.mjs', + bare: '/baremux/index.mjs', + }, + // The following two variables are copied and pasted here from csel.js. + readCookie = async (name) => { + // Get the first cookie that has the same name. + for (let cookie of document.cookie.split('; ')) + if (!cookie.indexOf(name + '=')) + // Return the cookie's stored content. + return decodeURIComponent(cookie.slice(name.length + 1)); + }, + // Sets the default transport mode 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'; - transports.default = transports[defaultMode]; +transports.default = transports[defaultMode]; - // Prevent the transports object from accidentally being edited. - Object.freeze(transports); +// Prevent the transports object from accidentally being edited. +Object.freeze(transports); - const registerSW = async () => { - if (!navigator.serviceWorker) { - if ( - location.protocol !== 'https:' && - !swAllowedHostnames.includes(location.hostname) - ) - throw new Error('Service workers cannot be registered without https.'); +const registerSW = async () => { + if (!navigator.serviceWorker) { + if ( + location.protocol !== 'https:' && + !swAllowedHostnames.includes(location.hostname) + ) + 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. - const transportMode = - transports[await readCookie('HBTransport')] || transports.default; - let transportOptions = { wisp: wispUrl }; + // If the user has changed the transport mode, use that over the default. + const transportMode = + transports[await readCookie('HBTransport')] || transports.default; + let transportOptions = { wisp: wispUrl }; - // Only use Tor with the proxy if the user has enabled it in settings. - if ((await readCookie('HBUseOnion')) === 'true') - transportOptions.proxy = proxyUrl; + // Only use Tor with the proxy if the user has enabled it in settings. + if ((await readCookie('HBUseOnion')) === 'true') + 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 - // 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. - const registrations = await navigator.serviceWorker.getRegistrations(), - usedSW = - (await readCookie('HBHideAds')) !== 'false' ? blacklistSW : stockSW; + /* 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 + * to be reloaded for this to update, such as by refreshing the page. + */ + const registrations = await navigator.serviceWorker.getRegistrations(), + usedSW = + (await readCookie('HBHideAds')) !== 'false' ? blacklistSW : stockSW; - // Unregister a service worker if it isn't the one being used. - for (const registration of registrations) - if ( - registration.active && - new URL(registration.active.scriptURL).pathname !== - new URL(usedSW, location.origin).pathname - ) - await registration.unregister(); + // Unregister a service worker if it isn't the one being used. + for (const registration of registrations) + if ( + registration.active && + new URL(registration.active.scriptURL).pathname !== + new URL(usedSW, location.origin).pathname + ) + await registration.unregister(); - await navigator.serviceWorker.register(usedSW); - }; + await navigator.serviceWorker.register(usedSW); +}; - /* +/* Commented out upon discovering that a duplicate BareMux connection may be 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(); */ - registerSW(); +registerSW(); })();