Skip to content

Instantly share code, notes, and snippets.

@annibal
Created August 14, 2024 20:37
Show Gist options
  • Save annibal/712e3981ad5eaefb877bc6e243916bbb to your computer and use it in GitHub Desktop.
Save annibal/712e3981ad5eaefb877bc6e243916bbb to your computer and use it in GitHub Desktop.
EXIT - Prevent Leaving webpage andmake requests informing of such action - An Proof of Concept
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ONBEFORE UN LOADDDD</title>
<style>
:root {
--border-radius: 4px;
--turquoise: #1abc9c;
--turquoise-rgb: 26, 188, 156;
--turquoise-dark: #16a085;
--turquoise-dark-rgb: 22, 160, 133;
--emerald: #2ecc71;
--emerald-rgb: 46, 204, 113;
--emerald-dark: #27ae60;
--emerald-dark-rgb: 39, 174, 96;
--peterriver: #3498db;
--peterriver-rgb: 52, 152, 219;
--peterriver-dark: #2980b9;
--peterriver-dark-rgb: 41, 128, 185;
--amethist: #9b59b6;
--amethist-rgb: 155, 89, 182;
--amethist-dark: #8e44ad;
--amethist-dark-rgb: 142, 68, 173;
--wetasphalt: #34495e;
--wetasphalt-rgb: 52, 73, 94;
--wetasphalt-dark: #2c3e50;
--wetasphalt-dark-rgb: 44, 62, 80;
--sunflower: #f1c40f;
--sunflower-rgb: 241, 196, 15;
--sunflower-dark: #f39c12;
--sunflower-dark-rgb: 243, 156, 18;
--carrot: #e67e22;
--carrot-rgb: 230, 126, 34;
--carrot-dark: #d35400;
--carrot-dark-rgb: 211, 84, 0;
--alizarin: #e74c3c;
--alizarin-rgb: 231, 76, 60;
--alizarin-dark: #c0392b;
--alizarin-dark-rgb: 192, 57, 43;
--clouds: #ecf0f1;
--clouds-rgb: 236, 240, 241;
--clouds-dark: #bdc3c7;
--clouds-dark-rgb: 189, 195, 199;
--concrete: #95a5a6;
--concrete-rgb: 149, 165, 166;
--concrete-dark: #7f8c8d;
--concrete-dark-rgb: 127, 140, 141;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
font-family: "Source Code Pro", MonoSpace;
font-size: 18px;
background: #fff;
color: var(--wetasphalt);
position: relative;
}
.nibol-container {
max-width: 768px;
padding: 1rem;
margin: 0 auto;
}
.nibol-card {
background: var(--clouds);
box-shadow: 0.2rem 0.2rem 0 var(--clouds-dark);
padding: 1rem;
border-radius: var(--border-radius);
margin: 0 0 1rem 0;
position: relative;
}
.nibol-card-turquoise {
background: var(--turquoise);
box-shadow: 0.2rem 0.2rem var(--turquoise-dark);
color: var(--clouds);
}
.nibol-card-emerald {
background: var(--emerald);
box-shadow: 0.2rem 0.2rem var(--emerald-dark);
color: var(--clouds);
}
.nibol-card-peterriver {
background: var(--peterriver);
box-shadow: 0.2rem 0.2rem var(--peterriver-dark);
color: var(--clouds);
}
.nibol-card-amethist {
background: var(--amethist);
box-shadow: 0.2rem 0.2rem var(--amethist-dark);
color: var(--clouds);
}
.nibol-card-wetasphalt {
background: var(--wetasphalt);
box-shadow: 0.2rem 0.2rem var(--wetasphalt-dark);
color: var(--clouds);
}
.nibol-card-sunflower {
background: var(--sunflower);
box-shadow: 0.2rem 0.2rem var(--sunflower-dark);
color: var(--clouds);
}
.nibol-card-carrot {
background: var(--carrot);
box-shadow: 0.2rem 0.2rem var(--carrot-dark);
color: var(--clouds);
}
.nibol-card-alizarin {
background: var(--alizarin);
box-shadow: 0.2rem 0.2rem var(--alizarin-dark);
color: var(--clouds);
}
.nibol-card-concrete {
background: var(--concrete);
box-shadow: 0.2rem 0.2rem var(--concrete-dark);
color: var(--clouds);
}
.nibol-card-header {
font-size: 1rem;
line-height: 1.2;
padding-bottom: 1rem;
border-bottom: 3px solid rgba(220, 220, 220, 0.2);
}
.nibol-card-content {
display: flex;
flex-wrap: wrap;
padding-top: 1rem;
gap: 1rem;
}
.nibol-button {
background: rgba(var(--turquoise-rgb), 0.8);
color: rgba(var(--clouds-rgb), 0.8);
box-shadow: 0.2rem 0.2rem 0 var(--turquoise-dark);
color: #fff;
line-height: 1;
letter-spacing: 1px;
font-size: 1rem;
font-family: inherit;
border: none;
text-transform: uppercase;
cursor: pointer;
padding: 0.75em 1em;
border-radius: var(--border-radius);
transition: transform 600ms cubic-bezier(0.44, 1.99, 0.12, 0.6),
box-shadow 600ms cubic-bezier(0.44, 1.99, 0.12, 0.6);
}
.nibol-button:hover,
.nibol-button:focus {
background: rgba(var(--turquoise-rgb), 1);
color: rgba(var(--clouds-rgb), 1);
}
.nibol-button:active {
box-shadow: 0.025rem 0.025rem 0 var(--turquoise-dark);
transform: translate(0.175rem, 0.175rem);
transition: transform 100ms linear, box-shadow 100ms linear;
}
.nibol-display {
border: 0.5em solid var(--wetasphalt);
border-bottom-width: 0.8em;
border-top-width: 0.8em;
box-shadow: 0.2em 0.2em 0 var(--wetasphalt-dark);
color: rgba(0, 0, 0, 0.8);
background-color: var(--emerald-dark);
background-image: linear-gradient(
to right,
transparent 0.55em,
var(--emerald) 0.55em
),
linear-gradient(to bottom, transparent 1.1em, var(--emerald) 1.1em);
background-size: calc(0.552em + 1px) calc(1.1em + 1px);
background-position: 0.5em 0.5em;
padding: 0.5em;
line-height: calc(1.1em + 1px);
min-height: calc(1em + 1.6em + 2.2em + 1px);
letter-spacing: 1px;
min-width: calc(1em + 1.6em + calc(0.55em * 16) + 16px);
display: inline-block;
position: relative;
}
.nibol-display:after {
position: absolute;
top: -0.5em;
left: -0.5em;
bottom: -0.5em;
right: -0.5em;
width: calc(100% + 1em);
height: calc(100% + 1em);
border: 0.5em solid var(--wetasphalt);
border-radius: calc(var(--border-radius) * 4);
display: block;
content: "";
pointer-events: none;
box-shadow: inset -1px -1px 0 rgba(255, 255, 255, 0.2),
inset 1px 1px 0 rgba(var(--wetasphalt-dark-rgb), 0.5),
inset 0.05em 0.05em 1px 0.15em rgba(var(--emerald-dark-rgb), 0.9),
inset 0.55em 0 0 var(--emerald), inset -0.55em 0 0 var(--emerald),
inset 0 0.6em 0 var(--emerald), inset 0 -0.6em 0 var(--emerald);
}
</style>
</head>
<body>
<br />
<section class="nibol-container">
<article class="nibol-card">
<header class="nibol-card-header">
<b>Prevent Tab Leaving</b>
<br />
When active, prevents the current tab from being closed by invoking an
confirmation dialog.
</header>
<main class="nibol-card-content">
<button id="btn-toggle-prevent" type="button" class="nibol-button">
Toggle Prevent
</button>
<div
id="display-toggle-prevent"
style="width: 220px"
class="nibol-display"
>
Nibol This Play
</div>
</main>
</article>
<article class="nibol-card">
<header class="nibol-card-header">
<b>Test Simple Server</b>
<br />
Click to make a request to localhost:42690.
</header>
<main class="nibol-card-content">
<button
id="btn-test-simple-server"
type="button"
class="nibol-button"
>
Send Request
</button>
</main>
</article>
</section>
<script>
console.clear();
document.addEventListener("DOMContentLoaded", initExitPoC);
const settings = {
preventLeaving: false,
isPreventionPopupShowing: false,
};
const refs = {
btnTogglePrevent: null,
btnTestSimpleServer: null,
displayTogglePrevent: null,
};
const rnd = (m = 6) => Math.floor(Math.random() * 10 ** m);
// Prevent Leaving (
window.addEventListener("focus", (e) => {
// console.log("focused", document.activeElement)
if (settings.isPreventionPopupShowing) {
leRequisicion("FOCUSED_AFTER_PREXIT_DIALOG");
}
settings.isPreventionPopupShowing = false;
document.body.setAttribute("style", "");
});
function leRequisicion(arg) {
try {
fetch(`http://localhost:42690?X=${rnd()}&Y=${rnd()}&arg=${arg}`)
.then((r) => r.json())
.then((res) => {
console.log(`leRequisición(${arg}) :>>`, res);
return res;
});
} catch(err) {
console.log(`le Requisición (${arg}) has FAILED.`, err);
}
}
function handlePreventTabLeaving(event) {
event.preventDefault();
const msg = "Are you sure?";
settings.isPreventionPopupShowing = true;
document.body.style.backgroundColor = "red";
leRequisicion("OPENED_PREXIT_DIALOG");
// confirm(`> ${msg}`)
event.returnValue = msg;
return msg;
// handler caso clique em sair
// o que acontece se ligar o checkbox "impedir popups?"
// se der pra mandar request ao clicar em "sair" -> beleza
// se não der ->
// mandar request antes de subir o popup
// mandar request caso clique em cancelar
}
function handleUnLoad(event) {
leRequisicion("UNLOADED_AND_EXITED");
}
function renderTogglePrevent() {
if (settings.preventLeaving) {
refs.displayTogglePrevent.innerText = "Prevention is Active";
refs.btnTogglePrevent.innerText = "Disable";
window.addEventListener("beforeunload", handlePreventTabLeaving);
} else {
refs.displayTogglePrevent.innerText = "Currently not preventing";
refs.btnTogglePrevent.innerText = "Turn on";
window.removeEventListener("beforeunload", handlePreventTabLeaving);
}
}
function handleBtnTogglePreventClick() {
settings.preventLeaving = !settings.preventLeaving;
renderTogglePrevent();
}
function initTogglePrevent() {
refs.btnTogglePrevent.removeEventListener(
"click",
handleBtnTogglePreventClick
);
refs.btnTogglePrevent.addEventListener(
"click",
handleBtnTogglePreventClick
);
renderTogglePrevent();
leRequisicion("INITIALIZED");
window.removeEventListener("unload", handleUnLoad);
window.addEventListener("unload", handleUnLoad);
}
// ) Prevent Leaving
function handleBtnTestSimpleServerClick() {
leRequisicion("VIBE_CHECK");
}
function initTestSimpleServer() {
refs.btnTestSimpleServer.removeEventListener(
"click",
handleBtnTestSimpleServerClick
);
refs.btnTestSimpleServer.addEventListener(
"click",
handleBtnTestSimpleServerClick
);
renderTogglePrevent();
}
function initExitPoC() {
refs.btnTogglePrevent = document.getElementById("btn-toggle-prevent");
refs.btnTestSimpleServer = document.getElementById(
"btn-test-simple-server"
);
refs.displayTogglePrevent = document.getElementById(
"display-toggle-prevent"
);
if (Object.values(refs).some((ref) => ref == null)) {
console.log("Required Element Refs: ", refs);
throw new Error("Failed to find all DOM element references");
}
initTogglePrevent();
initTestSimpleServer();
}
</script>
</body>
</html>
const http = require("http");
const url = require("url");
// Usage:
// node ./start_simple_server.js --port=42690
function fmtdt(now) {
const formattedDate = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2,  
'0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(4, '0')}`;  
return formattedDate
}
const startServer = (port) => {
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
console.log(`>: ò_ó 🫱 ${req.method} at ${fmtdt(new Date())}:`);
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
return;
}
const query = url.parse(req.url, true).query || {};
console.log(`>: { ${Object.entries(query)
.map(([k, v]) => `${k}: ${v}`)
.join(", ")} }`
);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: true, uptime: process.uptime() }));
});
server.listen(port, () => {
console.log(`>: ò_ó 🫱 SIMPLE SERVER STARTED OwO!! It's on port ${port}!!!
>: ò_ó 🫱 http://localhost:${port}/
>: ò_ó LISTENING!\n`);
});
};
const argv = process.argv.slice(2);
const port =
argv.find((arg) => arg.startsWith("--port="))?.split("=")[1] || 3000;
startServer(parseInt(port));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment