Merge pull request #76 from cohenerickson/main

This commit is contained in:
Cohen Erickson 2023-02-10 16:34:09 -06:00 committed by GitHub
commit 9de8d5fa48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2275 additions and 4518 deletions

3
.gitignore vendored
View file

@ -5,3 +5,6 @@
.DS_Store
Thumbs.db
.breakpoints
memory.txt
old.app.js

View file

@ -11,7 +11,7 @@ NebulaWeb is an official flagship of Nebula Services and Nebula Developer Labs.
- Stunning and highly functional UI with multiple themes
- XOR/b64 encoding all traffic
- Hides your IP from sites
- [List of officially supported sites](https://github.com/NebulaServices/Nebula/blob/main/docs/officially-supported-sites.md)
- [List of officially supported sites](https://github.com/NebulaServices/Nebula/blob/dev/docs/officially-supported-sites.md)
- _limited_ mobile support
- Stealth Mode (buffed `about:blank` cloaking)
- **NEW** Clickoff cloaking
@ -33,8 +33,6 @@ Table of contents
<br>
[![Run on Replit](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/replit.svg)](https://replit.com/github/NebulaServices/Nebula)
<br>
[![Remix on Glitch](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/glitch.svg)](https://glitch.com/edit/#!/import/github/NebulaServices/Nebula)
<br>
[![Deploy to IBM Cloud](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/ibmcloud.svg)](https://cloud.ibm.com/devops/setup/deploy?repository=https://github.com/NebulaServices/Nebula)
<br>
[![Deploy to Amplify Console](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/amplifyconsole.svg)](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/NebulaServices/Nebula)
@ -174,21 +172,21 @@ _Note: Server will need to run` cd Nebula && sudo nohup PORT=80 node . &` on reb
| **File** | Purpose | |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- | --- |
| `static/index.html` | The main frontend visuals for NebulaWEB. | |
| `static/unv.html` | The verification-required frontend/visuals. | |
| `static/options/` | The frontend for Nebula's options, settings, and preferences. | |
| `static/resources/v.js` | Client verification system for the OTP system. | |
| `static/resources/nebulamain.js` | All of the DOM/client code for NebulaWEB. Includes options, themeSystem, cloak, stealthengine, and more. | |
| `src/index.html` | The main frontend visuals for NebulaWEB. | |
| `src/unv.html` | The verification-required frontend/visuals. | |
| `src/options.html` | The frontend for Nebula's options, settings, and preferences. | |
| `public/resources/v.js` | Client verification system for the OTP system. | |
| `public/resources/nebulamain.js` | All of the DOM/client code for NebulaWEB. Includes options, themeSystem, cloak, stealthengine, and more. | |
| `app.js` | The backend server for Nebula. Contains Nodestatic, Bare, HTTP, and more. | |
## Tech Stack
- HTML, JS, CSS
- Partical.JS (Specifically v4, 5, 6.1 &< only)
- UV Backend Proxy
- Osana Backend Proxy
- TompHTTP Bare Server
- node HTTP (No ExpressJS!)
- Ultraviolet (proxy)
- Osana (proxy)
- TompHTTP Bare Server Node
- ExpressJS
## Support
@ -200,9 +198,9 @@ For support, email chloe@nebula.bio or join our discord: discord.gg/unblocker
## Acknowledgements
- [UV (one of the back-end proxy we use)](https://github.com/titaniumnetwork-dev/Ultraviolet)
- [Osana (one of the back-end proxy we use)](https://github.com/NebulaServices/Osana)
- [Bare Server](https://github.com/tomphttp/bare-server-node)
- [UV (one of the proxies use)](https://github.com/titaniumnetwork-dev/Ultraviolet)
- [Osana (one of the proxies we use)](https://github.com/NebulaServices/Osana)
- [Bare Server Node](https://github.com/tomphttp/bare-server-node)
- [Partical.JS (v4, 5, 6.1 &< only)](https://github.com/VincentGarreau/particles.js)
## License

374
app.js
View file

@ -1,162 +1,209 @@
import express from "express";
import cookieParser from "cookie-parser";
import http from "node:http";
import createBareServer from "@tomphttp/bare-server-node";
import http from "http";
import { createRequire } from "module";
import { dirname, join } from "path";
import serveStatic from "serve-static";
import { fileURLToPath } from "url";
const require = createRequire(import.meta.url);
const config = require("./deployment.config.json");
import fs from "fs";
var base64data;
import sgTransport from "nodemailer-sendgrid-transport";
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
import path from "node:path";
import config from "./deployment.config.json" assert { type: "json" };
import sgMail from "@sendgrid/mail";
import nodemailer from "nodemailer";
import fetch from "node-fetch";
const options = {
auth: {
api_key: config.sendgrid_options.api_key
}
};
const sendgridMailerAgent = nodemailer.createTransport(sgTransport(options));
const smtpMailerAgent = nodemailer.createTransport(config.smtp_options);
function sendVerificationEmail(UUID, OTP) {
let email = {
to: "",
from: "",
subject: `NebulaWEB personal access code ${OTP}`,
text: `
####### ACCESS CODE (OTP) ${OTP} #######
####### DO NOT SHARE THIS CODE! #######
(this message is automated)`,
html: `
####### ACCESS CODE (OTP) ${OTP} #######
####### DO NOT SHARE THIS CODE! #######
(this message is automated)
`
};
if (config.sendgrid_verification == true) {
email.to = config.sendgrid_options.to_email;
email.from = config.sendgrid_options.sendFromEmail;
sendgridMailerAgent.sendMail(email, (err, res) => {
if (err) {
console.log(err);
}
console.log(res);
});
}
if (config.smtp_verification == true) {
email.to = config.smtp_options.to_email;
email.from = config.smtp_options.sendFromEmail;
smtpMailerAgent.sendMail(email, (err, res) => {
if (err) {
console.log(err);
}
console.log(res);
});
}
if (config.discord_verification == true) {
fetch(config.webhook_url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
content: `Your NebulaWEB access code is ${OTP}`
})
});
}
}
function getNewCode() {
var seq = (Math.floor(Math.random() * 10000) + 10000).toString().substring(1);
if (seq == "0") {
getNewCode();
}
return seq;
}
import * as uuid from "uuid";
import fs from "node:fs";
import bcrypt from "bcrypt";
const PORT = process.env.PORT || 3000;
const bareServer = createBareServer("/bare/", {
logErrors: false,
localAddress: undefined
});
const serve = serveStatic(
join(dirname(fileURLToPath(import.meta.url)), "static/"),
{
fallthrough: false,
maxAge: 5 * 60 * 1000
}
);
const __dirname = process.cwd();
const ACTIVE_CODES = new Set();
let TOKENS = fs
.readFileSync("./memory.txt", "utf-8")
.trim()
.split("\n")
.map((token) => {
const parts = token.split(":");
return {
id: parts[0],
token: parts[1],
expiration: parts[2]
};
});
const server = http.createServer();
const app = express(server);
const bareServer = createBareServer("/bare/");
server.on("request", (request, response) => {
try {
if (bareServer.shouldRoute(request)) {
bareServer.routeRequest(request, response);
} else {
let base64data;
const url = request.url;
if (url.startsWith("/sendNewCode")) {
const OTP = getNewCode();
fs.writeFile("./memory.txt", OTP, function (err) {
if (err) return console.log(err);
console.log(`Wrote OTP code to temp file`);
});
// Middleware
app.use(cookieParser());
app.use(express.json());
app.use(
express.urlencoded({
extended: true
})
);
fs.readFile("./memory.txt", "utf8", (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
// Verification
app.patch("/generate-otp", async (req, res) => {
if (
config.sendgrid_verification == true ||
config.discord_verification == true ||
config.smtp_verificaton == true
) {
const OTP = generateCode();
ACTIVE_CODES.add(OTP);
sendVerificationEmail("10", data);
let buff = new Buffer(data);
base64data = buff.toString("base64");
console.log("302");
response.writeHead(302, {
location: "/unv.html?c=" + base64data
});
response.end();
});
} else if (url.startsWith("/verification")) {
var body;
if (
config.sendgrid_verification == true ||
config.discord_verification == true ||
config.smtp_verificaton == true
) {
const body = "true";
response.writeHead(200, {
"Content-Length": Buffer.byteLength(body),
"Content-Type": "text/plain"
});
response.end(body);
} else {
const body = "false";
response.writeHead(200, {
"Content-Length": Buffer.byteLength(body),
"Content-Type": "text/plain"
});
response.end(body);
}
} else {
serve(request, response, (err) => {
response.writeHead(err?.statusCode || 500, null, {
"Content-Type": "text/plain"
});
response.end(err?.stack);
});
setTimeout(() => {
ACTIVE_CODES.delete(OTP);
}, 1000 * 60 * 5);
let email = {
to: "",
from: "",
subject: `NebulaWEB personal access code ${OTP}`,
text: `
####### ACCESS CODE (OTP) ${OTP} #######
####### DO NOT SHARE THIS CODE! #######
(this message is automated)`
};
if (config.sendgrid_verification == true) {
sgMail.setApiKey(config.sendgrid_options.api_key);
email.to = config.sendgrid_options.to_email;
email.from = config.sendgrid_options.sendFromEmail;
try {
await sgMail.send(msg);
} catch {
return res.status(504).end();
}
}
} catch (e) {
response.writeHead(500, "Internal Server Error", {
"Content-Type": "text/plain"
});
response.end(e.stack);
if (config.smtp_verification == true) {
const smtpMailerAgent = nodemailer.createTransport(config.smtp_options);
email.to = config.smtp_options.to_email;
email.from = config.smtp_options.sendFromEmail;
try {
smtpMailerAgent.sendMail(email);
} catch {
return res.status(504).end();
}
}
if (config.discord_verification == true) {
try {
await fetch(config.webhook_url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
content: `Your NebulaWEB access code is \`${OTP}\``
})
});
} catch {
return res.status(500).end();
}
}
res.status(200).end();
} else {
res.status(404).end();
}
});
function generateCode() {
const code = Math.floor(Math.random() * 1000000);
return code.toString().padStart(6, "0");
}
app.post("/validate-otp", (req, res) => {
if (
config.sendgrid_verification == true ||
config.discord_verification == true ||
config.smtp_verificaton == true
) {
const OTP = req.body.otp;
if (ACTIVE_CODES.has(OTP)) {
ACTIVE_CODES.delete(OTP);
const token = uuid.v4();
TOKENS.push({
id: OTP,
token: hash(token),
expiration: Date.now() + 1000 * 60 * 60 * 24 * 30
});
fs.writeFileSync(
"./memory.txt",
TOKENS.map((token) => {
return `${token.id}:${token.token}:${token.expiration}`;
}).join("\n"),
"utf-8"
);
res.status(200).json({
success: true,
validation: `${OTP}:${token}`
});
} else {
res.status(401).json({
success: false
});
}
} else {
res.status(404).end();
}
});
// Static files
app.use("/uv/", express.static(uvPath));
app.use(express.static(path.join(__dirname, "public")));
// Login route
app.get("/login", (req, res) => {
if (
config.sendgrid_verification == true ||
config.discord_verification == true ||
config.smtp_verificaton == true
) {
res.sendFile(path.join(__dirname, "src", "unv.html"));
} else {
res.redirect("/");
}
});
// General Routes
app.use((req, res, next) => {
const verification = req.cookies["validation"];
if (!verification || !validateToken(verification)) {
res.redirect("/login");
} else {
next();
}
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "src", "index.html"));
});
app.get("/options", (req, res) => {
res.sendFile(path.join(__dirname, "src", "options.html"));
});
app.get("/privacy", (req, res) => {
res.sendFile(path.join(__dirname, "src", "privacy.html"));
});
// Bare Server
server.on("request", (req, res) => {
if (bareServer.shouldRoute(req)) {
bareServer.routeRequest(req, res);
} else {
app(req, res);
}
});
server.on("upgrade", (req, socket, head) => {
if (bareServer.shouldRoute(req)) {
bareServer.routeUpgrade(req, socket, head);
@ -165,14 +212,31 @@ server.on("upgrade", (req, socket, head) => {
}
});
server.listen(PORT);
server.on("listening", () => {
console.log(`Server running at http://localhost:${PORT}/.`);
});
if (process.env.UNSAFE_CONTINUE)
process.on("uncaughtException", (err, origin) => {
console.error(`Critical error (${origin}):`);
console.error(err);
console.error("UNSAFELY CONTINUING EXECUTION");
console.error();
});
server.listen({
port: PORT
});
console.log(`Server running at http://localhost:${PORT}/.`);
function hash(token) {
const salt = bcrypt.genSaltSync(10);
return bcrypt.hashSync(token, salt);
}
function validateToken(verification) {
console.log(verification);
const [id, token] = verification.split(":");
const tokenData = TOKENS.find((token) => token.id == id);
if (!tokenData) {
return false;
}
if (tokenData.expiration < Date.now()) {
return false;
}
return bcrypt.compareSync(token, tokenData.token);
}

View file

@ -1 +0,0 @@
5003

3849
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,11 +13,14 @@
"author": "Nebula Services",
"license": "AGPL-3.0-only",
"dependencies": {
"@tomphttp/bare-server-node": "^1.2.2",
"node-fetch": "^3.3.0",
"nodemailer": "^6.8.0",
"nodemailer-sendgrid-transport": "^0.2.0",
"serve-static": "^1.15.0"
"@sendgrid/mail": "^7.7.0",
"@titaniumnetwork-dev/ultraviolet": "^1.0.8-beta",
"@tomphttp/bare-server-node": "^1.2.3",
"bcrypt": "^5.1.0",
"cookie-parser": "^1.4.6",
"express": "^4.18.2",
"nodemailer": "^6.9.1",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=18"

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

@ -310,42 +310,6 @@ if (storedSetTheme == null) {
}
window.onload = function () {
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function httpGet(theUrl) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", theUrl, false); // false for synchronous request
xmlHttp.send(null);
return xmlHttp.responseText;
}
if (httpGet("/verification") == "true") {
if (getCookie("verifiedAccess") == "") {
console.log("COOKIE NOT FOUND - ENTRY NOT PERMITTED");
window.location = "/unv.html";
} else {
console.log("COOKIE RECOGNIZED - ENTRY PERMITTED ");
}
}
let background = localStorage.getItem("--background-primary");
let navbar = localStorage.getItem("--navbar-color");
let navbarHeight = localStorage.getItem("--navbar-height");

41
public/resources/v.js Normal file
View file

@ -0,0 +1,41 @@
const generateOTP = document.getElementById("generate-otp");
generateOTP.onclick = () => {
fetch("/generate-otp", { method: "PATCH" });
};
const validateOTP = document.getElementById("validate-otp");
validateOTP.onclick = () => {
fetch("/validate-otp", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
otp: document.getElementById("otp").value
})
}).then((response) => {
return response.json();
}).then((data) => {
if (data.success) {
setCookie("validation", data.validation, 30);
location.href = "/";
} else {
alert("Invalid OTP.");
}
}).catch(() => {
alert("An error occurred while validating your OTP.")
});
};
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}

10
public/uv/uv.config.js Normal file
View file

@ -0,0 +1,10 @@
self.__uv$config = {
prefix: "/service/go/",
bare: "/bare/",
encodeUrl: Ultraviolet.codec.xor.encode,
decodeUrl: Ultraviolet.codec.xor.decode,
handler: "/uv/uv.handler.js",
bundle: "/uv/uv.bundle.js",
config: "/uv/uv.config.js",
sw: "/uv/uv.sw.js"
};

306
src/options.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -23,7 +23,7 @@
"
>
<button
id="send-code"
id="generate-otp"
style="
background: #eb6f92;
border-radius: 3px;
@ -53,7 +53,7 @@
/>
</svg>
<input
id="codeBox"
id="otp"
type="text"
placeholder="Enter the code sent to your email"
style="
@ -85,7 +85,7 @@
/>
</svg>
<button
id="checkCode"
id="validate-otp"
style="
background: #eb6f92;
border-radius: 3px;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
self.__uv$config = {
prefix: '/service/go/',
bare: '/bare/',
encodeUrl: Ultraviolet.codec.xor.encode,
decodeUrl: Ultraviolet.codec.xor.decode,
handler: '/uv/uv.handler.js',
bundle: '/uv/uv.bundle.js',
config: '/uv/uv.config.js',
sw: '/uv/uv.sw.js',
};

File diff suppressed because it is too large Load diff

View file

@ -1,789 +0,0 @@
importScripts("/uv/uv.bundle.js");
importScripts("/uv/uv.config.js");
class UVServiceWorker extends EventEmitter {
constructor(config = __uv$config) {
super();
if (!config.bare) config.bare = '/bare/';
this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location));
this.headers = {
csp: [
'cross-origin-embedder-policy',
'cross-origin-opener-policy',
'cross-origin-resource-policy',
'content-security-policy',
'content-security-policy-report-only',
'expect-ct',
'feature-policy',
'origin-isolation',
'strict-transport-security',
'upgrade-insecure-requests',
'x-content-type-options',
'x-download-options',
'x-frame-options',
'x-permitted-cross-domain-policies',
'x-powered-by',
'x-xss-protection',
],
forward: [
'accept-encoding',
'connection',
'content-length',
],
};
this.method = {
empty: [
'GET',
'HEAD'
]
};
this.statusCode = {
empty: [
204,
304,
],
};
this.config = config;
this.browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName();
if (this.browser === 'Firefox') {
this.headers.forward.push('user-agent');
this.headers.forward.push('content-type');
};
};
async fetch({ request }) {
if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) {
return fetch(request);
};
try {
const ultraviolet = new Ultraviolet(this.config);
if (typeof this.config.construct === 'function') {
this.config.construct(ultraviolet, 'service');
};
const db = await ultraviolet.cookie.db();
ultraviolet.meta.origin = location.origin;
ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url));
const requestCtx = new RequestContext(
request,
this,
ultraviolet,
!this.method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null
);
if (ultraviolet.meta.url.protocol === 'blob:') {
requestCtx.blob = true;
requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname);
};
if (request.referrer && request.referrer.startsWith(location.origin)) {
const referer = new URL(ultraviolet.sourceUrl(request.referrer));
if (requestCtx.headers.origin || ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') {
requestCtx.headers.origin = referer.origin;
};
requestCtx.headers.referer = referer.href;
};
const cookies = await ultraviolet.cookie.getCookies(db) || [];
const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false);
if (this.browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) {
requestCtx.forward.shift();
};
if (cookieStr) requestCtx.headers.cookie = cookieStr;
requestCtx.headers.Host = requestCtx.url.host;
const reqEvent = new HookEvent(requestCtx, null, null);
this.emit('request', reqEvent);
if (reqEvent.intercepted) return reqEvent.returnValue;
const response = await fetch(requestCtx.send);
if (response.status === 500) {
return Promise.reject('');
};
const responseCtx = new ResponseContext(requestCtx, response, this);
const resEvent = new HookEvent(responseCtx, null, null);
this.emit('beforemod', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
for (const name of this.headers.csp) {
if (responseCtx.headers[name]) delete responseCtx.headers[name];
};
if (responseCtx.headers.location) {
responseCtx.headers.location = ultraviolet.rewriteUrl(responseCtx.headers.location);
};
if (responseCtx.headers['set-cookie']) {
Promise.resolve(ultraviolet.cookie.setCookies(responseCtx.headers['set-cookie'], db, ultraviolet.meta)).then(() => {
self.clients.matchAll().then(function (clients){
clients.forEach(function(client){
client.postMessage({
msg: 'updateCookies',
url: ultraviolet.meta.url.href,
});
});
});
});
delete responseCtx.headers['set-cookie'];
};
if (responseCtx.body) {
switch(request.destination) {
case 'script':
case 'worker':
responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`;
responseCtx.body += ultraviolet.js.rewrite(
await response.text()
);
break;
case 'style':
responseCtx.body = ultraviolet.rewriteCSS(
await response.text()
);
break;
case 'iframe':
case 'document':
if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) {
responseCtx.body = ultraviolet.rewriteHtml(
await response.text(),
{
document: true ,
injectHead: ultraviolet.createHtmlInject(
this.config.handler,
this.config.bundle,
this.config.config,
ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true),
request.referrer
)
}
);
};
};
};
if (requestCtx.headers.accept === 'text/event-stream') {
responseCtx.headers['content-type'] = 'text/event-stream';
};
this.emit('response', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
return new Response(responseCtx.body, {
headers: responseCtx.headers,
status: responseCtx.status,
statusText: responseCtx.statusText,
});
} catch(err) {
return new Response(err.toString(), {
status: 500,
});
};
};
getBarerResponse(response) {
const headers = {};
const raw = JSON.parse(response.headers.get('x-bare-headers'));
for (const key in raw) {
headers[key.toLowerCase()] = raw[key];
};
return {
headers,
status: +response.headers.get('x-bare-status'),
statusText: response.headers.get('x-bare-status-text'),
body: !this.statusCode.empty.includes(+response.headers.get('x-bare-status')) ? response.body : null,
};
};
get address() {
return this.addresses[Math.floor(Math.random() * this.addresses.length)];
};
static Ultraviolet = Ultraviolet;
};
self.UVServiceWorker = UVServiceWorker;
class ResponseContext {
constructor(request, response, worker) {
const { headers, status, statusText, body } = !request.blob ? worker.getBarerResponse(response) : {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries([...response.headers.entries()]),
body: response.body,
};
this.request = request;
this.raw = response;
this.ultraviolet = request.ultraviolet;
this.headers = headers;
this.status = status;
this.statusText = statusText;
this.body = body;
};
get url() {
return this.request.url;
}
get base() {
return this.request.base;
};
set base(val) {
this.request.base = val;
};
};
class RequestContext {
constructor(request, worker, ultraviolet, body = null) {
this.ultraviolet = ultraviolet;
this.request = request;
this.headers = Object.fromEntries([...request.headers.entries()]);
this.method = request.method;
this.forward = [...worker.headers.forward];
this.address = worker.address;
this.body = body || null;
this.redirect = request.redirect;
this.credentials = 'omit';
this.mode = request.mode === 'cors' ? request.mode : 'same-origin';
this.blob = false;
};
get send() {
return new Request((!this.blob ? this.address.href + 'v1/' : 'blob:' + location.origin + this.url.pathname), {
method: this.method,
headers: {
'x-bare-protocol': this.url.protocol,
'x-bare-host': this.url.hostname,
'x-bare-path': this.url.pathname + this.url.search,
'x-bare-port': this.url.port || (this.url.protocol === 'https:' ? '443' : '80'),
'x-bare-headers': JSON.stringify(this.headers),
'x-bare-forward-headers': JSON.stringify(this.forward),
},
redirect: this.redirect,
credentials: this.credentials,
mode: location.origin !== this.address.origin ? 'cors' : this.mode,
body: this.body
});
};
get url() {
return this.ultraviolet.meta.url;
};
set url(val) {
this.ultraviolet.meta.url = val;
};
get base() {
return this.ultraviolet.meta.base;
};
set base(val) {
this.ultraviolet.meta.base = val;
};
}
function isHtml(url, contentType = '') {
return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html';
};
class HookEvent {
#intercepted;
#returnValue;
constructor(data = {}, target = null, that = null) {
this.#intercepted = false;
this.#returnValue = null;
this.data = data;
this.target = target;
this.that = that;
};
get intercepted() {
return this.#intercepted;
};
get returnValue() {
return this.#returnValue;
};
respondWith(input) {
this.#returnValue = input;
this.#intercepted = true;
};
};
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args);
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target));
};
} else {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target);
};
}
function ProcessEmitWarning(warning) {
if (console && console.warn) console.warn(warning);
}
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value;
}
function EventEmitter() {
EventEmitter.init.call(this);
}
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
var defaultMaxListeners = 10;
function checkListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
}
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
}
defaultMaxListeners = arg;
}
});
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
}
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
EventEmitter.prototype.emit = function emit(type) {
var args = [];
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
var doError = (type === 'error');
var events = this._events;
if (events !== undefined)
doError = (doError && events.error === undefined);
else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
var er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
// At least give some kind of context to the user
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
err.context = er;
throw err; // Unhandled 'error' event
}
var handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
ReflectApply(handler, this, args);
} else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
ReflectApply(listeners[i], this, args);
}
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
ProcessEmitWarning(w);
}
}
return target;
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
var wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
checkListener(listener);
events = this._events;
if (events === undefined)
return this;
list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events, i;
events = this._events;
if (events === undefined)
return this;
// not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
var events = target._events;
if (events === undefined)
return [];
var evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events !== undefined) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
function once(emitter, name) {
return new Promise(function (resolve, reject) {
function errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
if (name !== 'error') {
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
}
});
}
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen for `error` events here.
emitter.addEventListener(name, function wrapListener(arg) {
// IE does not have builtin `{ once: true }` support so we
// have to do it manually.
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}