Created
August 14, 2024 20:37
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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