datadome, cloudflare, google and akamai on suicide watch
This commit is contained in:
559
ext/bg.js
Normal file
559
ext/bg.js
Normal 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
BIN
ext/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 400 B |
BIN
ext/icons/connected.png
Executable file
BIN
ext/icons/connected.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 969 B |
BIN
ext/icons/connecting.png
Normal file
BIN
ext/icons/connecting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 B |
BIN
ext/icons/offline.png
Executable file
BIN
ext/icons/offline.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 895 B |
30
ext/manifest.json
Normal file
30
ext/manifest.json
Normal 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
80
ext/popup-style.css
Normal 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
27
ext/popup.html
Normal 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&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
89
ext/popup.js
Normal 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 & ready to kick ass`;
|
||||
break;
|
||||
|
||||
case STATUS_OFFLINE:
|
||||
icon_path = "offline";
|
||||
text = `C&C unreachable. Attempt #${config.attempts}`;
|
||||
break;
|
||||
}
|
||||
|
||||
dom_status.innerHTML = `<img src="icons/${icon_path}.png">${text}`;
|
||||
}
|
||||
Reference in New Issue
Block a user