Working POC
This commit is contained in:
12
manifest.json
Normal file
12
manifest.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"name": "Cookie Monster",
|
||||||
|
"manifest_version": 3,
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Uses the chrome.cookies API by to allow for bulk edit/export/import/deletion of cookies",
|
||||||
|
"permissions": ["cookies"],
|
||||||
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
}
|
||||||
|
}
|
29
popup.html
Normal file
29
popup.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="popup.js" type="module"></script>
|
||||||
|
<style>
|
||||||
|
#cookieJson {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px dashed black;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
#cookieInput {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form id="control-row">
|
||||||
|
<label for="input">Domain:</label>
|
||||||
|
<input type="text" id="input" />
|
||||||
|
<br />
|
||||||
|
</form>
|
||||||
|
<textarea id="cookieInput" rows="20" cols="50"></textarea>
|
||||||
|
<button id="getCookiesButton">Get Cookies</button>
|
||||||
|
<button id="saveCookiesButton">Set Cookies</button>
|
||||||
|
<button id="go">Clear Cookies</button>
|
||||||
|
<pre id="cookieJson"></pre>
|
||||||
|
<span id="message" hidden></span>
|
||||||
|
</body>
|
||||||
|
</html>
|
145
popup.js
Normal file
145
popup.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
const form = document.getElementById('control-row');
|
||||||
|
const input = document.getElementById('input');
|
||||||
|
const message = document.getElementById('message');
|
||||||
|
const getCookieButton = document.getElementById('getCookiesButton');
|
||||||
|
const setCookieButton = document.getElementById('saveCookiesButton');
|
||||||
|
const cookieTextArea = document.getElementById('cookieInput');
|
||||||
|
const cookieDisplayArea = document.getElementById('cookieJson');
|
||||||
|
// The async IIFE is necessary because Chrome <89 does not support top level await.
|
||||||
|
(async function initPopupWindow() {
|
||||||
|
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
|
||||||
|
if (tab?.url) {
|
||||||
|
try {
|
||||||
|
let url = new URL(tab.url);
|
||||||
|
input.value = url.hostname;
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
})();
|
||||||
|
|
||||||
|
setCookieButton.addEventListener('click', async () => {
|
||||||
|
const cookieData = JSON.parse(cookieTextArea.value)
|
||||||
|
cookieData.map(async (cookie) => {
|
||||||
|
try {
|
||||||
|
let url = stringToUrl(input.value);
|
||||||
|
|
||||||
|
cookie.domain = url.hostname
|
||||||
|
cookie.url = `https://${url.hostname}${url.pathname}`
|
||||||
|
console.log('cookie', cookie)
|
||||||
|
delete cookie.hostOnly
|
||||||
|
delete cookie.session
|
||||||
|
await chrome.cookies.set(cookie)
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e)
|
||||||
|
}
|
||||||
|
console.log('set this thing')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
getCookieButton.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
let url = stringToUrl(input.value);
|
||||||
|
|
||||||
|
const cookies = await chrome.cookies.getAll({ domain: url.hostname });
|
||||||
|
|
||||||
|
if (cookies.length === 0) {
|
||||||
|
return 'No cookies found';
|
||||||
|
}
|
||||||
|
cookieDisplayArea.innerHTML = JSON.stringify(cookies, undefined, 2);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return `Unexpected error: ${error.message}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
form.addEventListener('submit', handleFormSubmit);
|
||||||
|
|
||||||
|
async function handleFormSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
clearMessage();
|
||||||
|
|
||||||
|
let url = stringToUrl(input.value);
|
||||||
|
if (!url) {
|
||||||
|
setMessage('Invalid URL');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = await deleteDomainCookies(url.hostname);
|
||||||
|
setMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToUrl(input) {
|
||||||
|
// Start with treating the provided value as a URL
|
||||||
|
try {
|
||||||
|
return new URL(input);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
// If that fails, try assuming the provided input is an HTTP host
|
||||||
|
try {
|
||||||
|
return new URL('http://' + input);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
// If that fails ¯\_(ツ)_/¯
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDomainCookies(domain) {
|
||||||
|
let cookiesDeleted = 0;
|
||||||
|
try {
|
||||||
|
const cookies = await chrome.cookies.getAll({ domain });
|
||||||
|
|
||||||
|
if (cookies.length === 0) {
|
||||||
|
return 'No cookies found';
|
||||||
|
}
|
||||||
|
|
||||||
|
let pending = cookies.map(deleteCookie);
|
||||||
|
await Promise.all(pending);
|
||||||
|
|
||||||
|
cookiesDeleted = pending.length;
|
||||||
|
} catch (error) {
|
||||||
|
return `Unexpected error: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Deleted ${cookiesDeleted} cookie(s).`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCookie(cookie) {
|
||||||
|
// Cookie deletion is largely modeled off of how deleting cookies works when using HTTP headers.
|
||||||
|
// Specific flags on the cookie object like `secure` or `hostOnly` are not exposed for deletion
|
||||||
|
// purposes. Instead, cookies are deleted by URL, name, and storeId. Unlike HTTP headers, though,
|
||||||
|
// we don't have to delete cookies by setting Max-Age=0; we have a method for that ;)
|
||||||
|
//
|
||||||
|
// To remove cookies set with a Secure attribute, we must provide the correct protocol in the
|
||||||
|
// details object's `url` property.
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure
|
||||||
|
const protocol = cookie.secure ? 'https:' : 'http:';
|
||||||
|
|
||||||
|
// Note that the final URL may not be valid. The domain value for a standard cookie is prefixed
|
||||||
|
// with a period (invalid) while cookies that are set to `cookie.hostOnly == true` do not have
|
||||||
|
// this prefix (valid).
|
||||||
|
// https://developer.chrome.com/docs/extensions/reference/cookies/#type-Cookie
|
||||||
|
const cookieUrl = `${protocol}//${cookie.domain}${cookie.path}`;
|
||||||
|
|
||||||
|
return chrome.cookies.remove({
|
||||||
|
url: cookieUrl,
|
||||||
|
name: cookie.name,
|
||||||
|
storeId: cookie.storeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMessage(str) {
|
||||||
|
message.textContent = str;
|
||||||
|
message.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMessage() {
|
||||||
|
message.hidden = true;
|
||||||
|
message.textContent = '';
|
||||||
|
}
|
Reference in New Issue
Block a user