datadome, cloudflare, google and akamai on suicide watch

This commit is contained in:
2026-06-01 03:31:51 -04:00
commit bedf56ba8c
12 changed files with 1671 additions and 0 deletions

559
ext/bg.js Normal file
View File

@@ -0,0 +1,559 @@
const STATUS_CONNECTING = 0;
const STATUS_OFFLINE = 1;
const STATUS_CONNECTED = 2;
var conn_attempts = 0;
var connected = false;
var timeoutId;
var global_ws = null;
var container_count = 0;
var proxy_map = {};
browser.browserAction.setBadgeBackgroundColor({
color: [0, 0, 0, 0]
});
// helper functions
async function get_tabs(){
var tabs = await browser.tabs.query({});
return tabs.map(tab => ({
id: tab.id,
index: tab.index,
status: tab.status,
active: tab.active,
title: tab.title,
url: tab.url,
container: tab.cookieStoreId
}));
}
async function tab_exists(id){
try{
await browser.tabs.get(id);
return true;
}catch{
return false;
}
}
async function get_container_list(){
const containers = await browser.contextualIdentities.query({});
var list = [];
for(var i=0; i<containers.length; i++){
list.push({
id: containers[i].cookieStoreId,
name: containers[i].name,
icon: containers[i].icon,
color: containers[i].color,
proxy: proxy_map[containers[i].cookieStoreId]
});
}
return list;
}
async function container_exists(id){
try{
await browser.contextualIdentities.get(id);
return true;
}catch{
return false;
}
}
function send(ws, seqid, msg = {}){
msg.seqid = seqid;
var msg = JSON.stringify(msg);
console.log("-> " + msg);
ws.send(msg);
}
function send_event(ws, msg = {}){
var msg = JSON.stringify(msg);
console.log("-> " + msg);
ws.send(msg);
}
async function set_status(status){
switch(status){
case STATUS_CONNECTING:
conn_attempts++;
browser.browserAction.setBadgeText({text: "🔵"});
await browser.storage.local.set({"status": STATUS_CONNECTING, "attempts": conn_attempts});
break;
case STATUS_CONNECTED:
conn_attempts = 0;
browser.browserAction.setBadgeText({text: "🟢"});
await browser.storage.local.set({"status": STATUS_CONNECTED, "attempts": 0});
break;
case STATUS_OFFLINE:
browser.browserAction.setBadgeText({text: "🔴"});
await browser.storage.local.set({"status": STATUS_OFFLINE});
break;
}
}
function ws_connect_timeout(url, timeoutMs = 5000){
timeoutMs = parseInt(timeoutMs);
return new Promise(function(resolve, reject){
var ws = null;
global_ws = null;
try{
ws = new WebSocket(url);
}catch(error){
setTimeout(async function(){
ws_init();
reject(new Error("Bad URL"));
}, timeoutMs);
return;
}
attach_ws_events(ws);
/*
ws.addEventListener("open", function(){console.log("open!");});
ws.addEventListener("message", function(){console.log("message!");});
ws.addEventListener("close", function(){console.log("close!");});*/
connected = false;
// Set up the timeout
timeoutId = setTimeout(function(){
if(!connected){
ws.close();
reject(new Error("Timeout"));
}
}, timeoutMs);
ws.addEventListener("open", function(){
connected = true;
clearTimeout(timeoutId);
resolve(ws);
});
ws.addEventListener("close", function(){
clearTimeout(timeoutId);
setTimeout(async function(){
ws_init();
reject(new Error("Close"));
}, timeoutMs);
});
});
}
async function ws_init(){
await set_status(STATUS_CONNECTING);
var config = await browser.storage.local.get();
try{
global_ws = await ws_connect_timeout(config.ws_url, config.ws_timeout);
}catch(error){
console.log("ws: " + error);
return;
}
// online
}
function attach_ws_events(ws){
ws.addEventListener("open", async function(){
console.log("ws: connected");
await set_status(STATUS_CONNECTED);
});
ws.addEventListener("message", async function(e){
console.log("<- " + e.data);
var msg = JSON.parse(e.data);
var seqid = msg.seqid;
switch(msg.action){
//
// Misc
//
case "get_ua":
send(ws, seqid, {
"ua": navigator.userAgent
});
break;
//
// Tabs
//
case "get_tabs":
send(ws, seqid, {
"tabs": await get_tabs()
});
break;
case "tab_open":
var tab =
await browser.tabs.create({
url: msg.url,
...(typeof msg.container == "string" && { cookieStoreId: msg.container })
});
// immediately return even if its not loaded yet
send(ws, seqid, {
data: {
id: tab.id,
index: tab.index,
status: tab.status,
active: tab.active,
title: tab.title,
url: tab.url,
container: tab.cookieStoreId
}
});
break;
case "tab_close":
var closed_tabs = 0;
switch(typeof msg.tabid){
case "number":
var exists = await tab_exists(msg.tabid);
if(exists){
await browser.tabs.remove(msg.tabid);
closed_tabs = 1;
}
break;
case "object":
var exists = false;
for(var i=0; i<msg.tabid.length; i++){
exists = await tab_exists(msg.tabid[i]);
if(exists){
await browser.tabs.remove(msg.tabid[i]);
closed_tabs++;
}
}
break;
}
send(ws, seqid, {"closed_tab_count": closed_tabs});
break;
case "tab_focus":
var exists = await tab_exists(msg.tabid);
if(exists){
await browser.tabs.update(msg.tabid, { active: true });
send(ws, seqid, {"status": true});
return;
}
send(ws, seqid, {"status": false});
break;
case "tab_exists":
var exists = await tab_exists(msg.tabid);
send(ws, seqid, {"exists": exists});
break;
case "tab_inject_js":
try{
var result = await browser.scripting.executeScript({
target: { tabId: msg.tabid },
func: (code) => eval(code),
args: [msg.js],
...(msg.isolated === true && { world: "ISOLATED" })
});
send(ws, seqid, {"status": true, "result": result});
}catch(err){
send(ws, seqid, {"status": err.name + ": " + err.message});
}
break;
//
// Containers
//
case "get_container_list":
var containers = await get_container_list();
send(ws, seqid, {
"containers": containers
});
break;
case "container_create":
// generate random container attributes
container_count++;
var name = null;
if(typeof msg.name != "undefined"){
name = msg.name;
}else{
name = "sesh" + container_count;
}
const color = [
"blue",
"turquoise",
"green",
"yellow",
"orange",
"red",
"pink",
"purple"
][Math.floor(Math.random() * 8)];
const icon = [
"fingerprint",
"briefcase",
"dollar",
"cart",
"circle",
"gift",
"vacation",
"food",
"fruit",
"pet",
"tree",
"chill",
"fence"
][Math.floor(Math.random() * 13)];
const container = await browser.contextualIdentities.create({
name: name,
color: color,
icon: icon
});
proxy_map[container.cookieStoreId] = {
type: "direct"
};
send(ws, seqid, {
id: container.cookieStoreId,
name: name,
color: color,
icon: icon,
proxy: proxy_map[container.cookieStoreId]
});
break;
case "container_exists":
var exists = await container_exists(msg.id);
send(ws, seqid, {"exists": exists});
break;
case "container_delete":
var deleted_containers = 0;
switch(typeof msg.id){
case "number":
var exists = await container_exists(msg.id);
if(exists){
await browser.contextualIdentities.remove(msg.id);
delete(proxy_map[msg.id]);
deleted_containers = 1;
}
break;
case "object":
var exists = false;
for(var i=0; i<msg.id.length; i++){
exists = await container_exists(msg.id[i]);
if(exists){
await browser.contextualIdentities.remove(msg.id[i]);
delete(proxy_map[msg.id[i]]);
deleted_containers++;
}
}
break;
}
send(ws, seqid, {"closed_container_count": deleted_containers});
break;
case "container_attach_proxy":
var status = await container_exists(msg.id);
if(status){
proxy_map[msg.id] = msg.proxy;
send(ws, seqid, {status: true});
return;
}
send(ws, seqid, {status: false});
break;
case "container_detach_proxy":
var status = await container_exists(msg.id);
if(status){
proxy_map[msg.id] = {type: "direct"};
send(ws, seqid, {status: true});
return;
}
send(ws, seqid, {status: false});
break;
default:
console.log(`ws: unhandled message "${msg.action}"`, msg);
break;
}
});
ws.addEventListener("close", async function(e){
console.log("ws: offline");
await set_status(STATUS_OFFLINE);
});
}
//
// Page events
//
browser.webRequest.onSendHeaders.addListener(
function(details){
var headers = [];
for(const header of details.requestHeaders){
headers.push(header.name + ": " + header.value);
}
send_event(
global_ws,
{
"action": "web_request",
"data": {
id: details.tabId,
url: details.url,
status: details.statusCode,
origin: details.originUrl,
type: details.type,
method: details.method,
container: details.cookieStoreId,
headers: headers
}
}
);
},
{urls: ["<all_urls>"], types: ["main_frame"]},
["requestHeaders"]
);
browser.proxy.onRequest.addListener(function(request){
const proxy_config = proxy_map[request.cookieStoreId];
if(proxy_config){
return proxy_config;
}
// fallback, should not happen
return {
type: "direct"
}
},
{
urls: ["<all_urls>"]
});
browser.tabs.onUpdated.addListener(function(tabid, event, tab){
if(connected === false){ return; }
if(event.status === "complete"){
send_event(
global_ws,
{
"action": "dom_ready",
"data": {
id: tab.id,
index: tab.index,
status: tab.status,
active: tab.active,
title: tab.title,
url: tab.url,
container: tab.cookieStoreId
}
}
);
}
});
browser.webNavigation.onErrorOccurred.addListener(function(page){
if(connected === false){ return; }
send_event(
global_ws,
{
"action": "dom_load_fail",
"data": {
id: page.tabId
}
}
);
});
(async function(){
await ws_init();
})();

BIN
ext/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

BIN
ext/icons/connected.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

BIN
ext/icons/connecting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

BIN
ext/icons/offline.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

30
ext/manifest.json Normal file
View File

@@ -0,0 +1,30 @@
{
"manifest_version": 2,
"name": "4play",
"version": "1.0",
"description": "4play & dominate",
"icons": {
"48": "icon.png"
},
"browser_action": {
"default_icon": "icon.png",
"default_title": "4play",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"contextualIdentities",
"cookies",
"proxy",
"webNavigation",
"webRequest",
"webRequestBlocking",
"scripting",
"<all_urls>",
"activeTab",
"storage"
],
"background": {
"scripts": ["bg.js"]
}
}

80
ext/popup-style.css Normal file
View File

@@ -0,0 +1,80 @@
body{
padding:0;
margin:0;
font-family:sans-serif;
font-size:14px;
color:#bdae93;
background:#282828;
min-width:350px;
}
.header{
padding:4px 10px;
font-weight:bold;
font-size:24px;
border-bottom:1px solid #282828;
background:#3c3836;
overflow:hidden;
line-height:32px;
font-family:monospace;
color:#d5c4a1;
}
.header img{
float:left;
margin-right:7px;
}
.content{
margin:10px 10px 27px 10px;
}
.title{
font-size:18px;
font-weight:bold;
display:block;
margin-bottom:10px;
}
.title::before{
content:"#";
color:#a89984;
}
label{
display:block;
color:#a89984;
}
.option-wrapper{
margin-bottom:10px;
}
input{
all:unset;
display:block;
width:100%;
background:#1d2021;
border:1px solid #504545;
padding:4px 7px;
box-sizing:border-box;
}
input:focus{
border-color:#928374;
}
#status{
font-size:14px;
overflow:hidden;
line-height:18px;
padding:4px 10px;
border-bottom:1px solid #504545;
background:#1d2021;
font-family:monospace;
}
#status img{
float:left;
margin-right:4px;
}

27
ext/popup.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup-style.css">
</head>
<body>
<div class="header">
<img src="icon.png">
4play
</div>
<div id="status"></div>
<form class="content">
<div class="option-wrapper">
<label for="ws_url">C&amp;C server</label>
<input type="text" name="ws_url" placeholder="Eg: ws://localhost:3030/cnc">
</div>
<div class="option-wrapper">
<label for="ws_url">Connect timeout (milliseconds)</label>
<input type="text" name="ws_timeout" placeholder="Eg: 5000">
</div>
</form>
<script src="popup.js"></script>
</body>
</html>

89
ext/popup.js Normal file
View File

@@ -0,0 +1,89 @@
// this code triggers on popup load
const STATUS_CONNECTING = 0;
const STATUS_OFFLINE = 1;
const STATUS_CONNECTED = 2;
document.addEventListener('DOMContentLoaded', async function(){
// generate initial config
var config = await browser.storage.local.get();
if(typeof config.ws_url == "undefined"){
await browser.storage.local.set({"ws_url": "ws://localhost:3030/cnc"});
}
if(typeof config.ws_timeout == "undefined"){
await browser.storage.local.set({"ws_timeout": 5000});
}
// update form
config = await browser.storage.local.get(); // refresh first
var form = document.getElementsByClassName("content")[0];
var form_ws_url = document.getElementsByName("ws_url")[0];
var form_ws_timeout = document.getElementsByName("ws_timeout")[0];
form_ws_url.value = config.ws_url;
form_ws_timeout.value = config.ws_timeout;
// attach events to both form inputs to update config
form_ws_url.addEventListener("input", async function(){
await browser.storage.local.set({"ws_url": form_ws_url.value});
});
form_ws_timeout.addEventListener("input", async function(e){
form_ws_timeout.value = form_ws_timeout.value.replaceAll(/[^0-9]/g, "");
var timeout = form_ws_timeout.value;
if(form_ws_timeout.value == ""){
timeout = 5000;
}
await browser.storage.local.set({"ws_timeout": timeout});
});
// show the websocket status
await html_gen_status();
setInterval(async function(){
await html_gen_status();
}, 1000);
});
async function html_gen_status(){
var dom_status = document.getElementById("status");
var icon_path = null;
var text = null;
var config = await browser.storage.local.get();
switch(config.status){
case STATUS_CONNECTING:
icon_path = "connecting";
text = `Connecting... Attempt #${config.attempts}`;
break;
case STATUS_CONNECTED:
icon_path = "connected";
text = `Connected &amp; ready to kick ass`;
break;
case STATUS_OFFLINE:
icon_path = "offline";
text = `C&amp;C unreachable. Attempt #${config.attempts}`;
break;
}
dom_status.innerHTML = `<img src="icons/${icon_path}.png">${text}`;
}