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 " + 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 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"], 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: [""] }); 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(); })();