├── .github ├── FUNDING.yml └── workflows │ └── npm-grunt.yml ├── interface ├── lib │ ├── isPopup.js │ ├── isDevtools.js │ ├── options │ │ ├── themes.js │ │ ├── exportFormats.js │ │ ├── extraInfos.js │ │ └── options.js │ ├── env.js │ ├── browsers.js │ ├── guid.js │ ├── eventEmitter.js │ ├── jsonFormat.js │ ├── ads │ │ ├── ad.js │ │ ├── adHandler.js │ │ └── activeAds.js │ ├── themeHandler.js │ ├── headerstringFormat.js │ ├── genericStorageHandler.js │ ├── netscapeFormat.js │ ├── browserDetector.js │ ├── permissionHandler.js │ └── genericCookieHandler.js ├── popup │ ├── pixel.png │ ├── dark.css │ └── cookieHandlerPopup.js ├── devtools │ ├── devtool.html │ ├── style.css │ ├── devtools.js │ ├── permissionHandler.js │ └── cookieHandlerDevtools.js ├── sidepanel │ └── style.css ├── popup-mobile │ └── dark.css ├── theme │ ├── switch.css │ ├── light.css │ └── dark.css └── options │ ├── style.css │ ├── options.js │ └── options.html ├── site ├── favicon.ico ├── robots.txt ├── img │ ├── cookie-add.png │ ├── cookie-edit.png │ ├── cookie-list.png │ ├── from-android.png │ ├── from-toolbar.png │ ├── cookie-delete.png │ ├── cookie-import.png │ ├── from-devtools.png │ ├── tab-for-cause.png │ ├── zoom.svg │ ├── edge-logo.svg │ ├── vivaldi-logo.svg │ ├── opera-logo.svg │ ├── github-logo.svg │ ├── cookie-filled.svg │ ├── digitalocean-vertical.svg │ └── chrome-logo.svg ├── sitemap.xml ├── 404.html └── privacy.html ├── readme ├── get-opera.png ├── get-chrome.png └── get-firefox.webp ├── .gitattributes ├── icons ├── cookie-128-filled.png ├── cookie-150-filled.png ├── cookie-16-filled.png ├── cookie-19-filled.png ├── cookie-32-filled.png ├── cookie-38-filled.png ├── cookie-38-legacy.png ├── cookie-44-filled.png ├── cookie-48-filled.png ├── cookie-50-filled.png ├── cookie-64-filled.png ├── cookie-small.svg ├── cookie-light-small.svg ├── cookie-filled-small.svg ├── cookie.svg ├── cookie-light.svg └── cookie-filled.svg ├── safari └── Cookie-Editor │ ├── Shared (App) │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── mac-icon-128@1x.png │ │ │ ├── mac-icon-128@2x.png │ │ │ ├── mac-icon-16@1x.png │ │ │ ├── mac-icon-16@2x.png │ │ │ ├── mac-icon-256@1x.png │ │ │ ├── mac-icon-256@2x.png │ │ │ ├── mac-icon-32@1x.png │ │ │ ├── mac-icon-32@2x.png │ │ │ ├── mac-icon-512@1x.png │ │ │ ├── mac-icon-512@2x.png │ │ │ ├── universal-icon-1024@1x.png │ │ │ └── Contents.json │ │ ├── LargeIcon.imageset │ │ │ ├── cookie-128-filled.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Resources │ │ ├── Icon.png │ │ ├── screenshot-ios.jpg │ │ ├── Style.css │ │ └── Script.js │ ├── Base.lproj │ │ └── Main.html │ └── ViewController.swift │ ├── Cookie-Editor.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── macOS (App) │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Cookie-Editor.entitlements │ └── Base.lproj │ │ └── Main.storyboard │ ├── iOS (App) │ ├── SceneDelegate.swift │ ├── AppDelegate.swift │ ├── Info.plist │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── macOS (Extension) │ ├── Cookie-Editor.entitlements │ └── Info.plist │ ├── iOS (Extension) │ └── Info.plist │ └── Shared (Extension) │ └── SafariWebExtensionHandler.swift ├── .prettierrc.json ├── .editorconfig ├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── package.json ├── manifest.edge.json ├── manifest.safari.json ├── manifest.opera.json ├── manifest.chrome.json ├── eslint.config.mjs ├── manifest.firefox.json ├── README.md └── CODE_OF_CONDUCT.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [moustachauve] 2 | -------------------------------------------------------------------------------- /interface/lib/isPopup.js: -------------------------------------------------------------------------------- 1 | window.isPopup = true; 2 | -------------------------------------------------------------------------------- /interface/lib/isDevtools.js: -------------------------------------------------------------------------------- 1 | window.isDevtools = true; 2 | -------------------------------------------------------------------------------- /site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/favicon.ico -------------------------------------------------------------------------------- /site/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | 4 | SITEMAP: https://cookie-editor.com/sitemap.xml -------------------------------------------------------------------------------- /readme/get-opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/readme/get-opera.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.ico binary 4 | *.jpg binary 5 | *.png binary 6 | *.webp binary -------------------------------------------------------------------------------- /readme/get-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/readme/get-chrome.png -------------------------------------------------------------------------------- /readme/get-firefox.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/readme/get-firefox.webp -------------------------------------------------------------------------------- /site/img/cookie-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/cookie-add.png -------------------------------------------------------------------------------- /interface/popup/pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/interface/popup/pixel.png -------------------------------------------------------------------------------- /site/img/cookie-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/cookie-edit.png -------------------------------------------------------------------------------- /site/img/cookie-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/cookie-list.png -------------------------------------------------------------------------------- /site/img/from-android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/from-android.png -------------------------------------------------------------------------------- /site/img/from-toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/from-toolbar.png -------------------------------------------------------------------------------- /icons/cookie-128-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-128-filled.png -------------------------------------------------------------------------------- /icons/cookie-150-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-150-filled.png -------------------------------------------------------------------------------- /icons/cookie-16-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-16-filled.png -------------------------------------------------------------------------------- /icons/cookie-19-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-19-filled.png -------------------------------------------------------------------------------- /icons/cookie-32-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-32-filled.png -------------------------------------------------------------------------------- /icons/cookie-38-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-38-filled.png -------------------------------------------------------------------------------- /icons/cookie-38-legacy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-38-legacy.png -------------------------------------------------------------------------------- /icons/cookie-44-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-44-filled.png -------------------------------------------------------------------------------- /icons/cookie-48-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-48-filled.png -------------------------------------------------------------------------------- /icons/cookie-50-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-50-filled.png -------------------------------------------------------------------------------- /icons/cookie-64-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/icons/cookie-64-filled.png -------------------------------------------------------------------------------- /site/img/cookie-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/cookie-delete.png -------------------------------------------------------------------------------- /site/img/cookie-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/cookie-import.png -------------------------------------------------------------------------------- /site/img/from-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/from-devtools.png -------------------------------------------------------------------------------- /site/img/tab-for-cause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/site/img/tab-for-cause.png -------------------------------------------------------------------------------- /interface/lib/options/themes.js: -------------------------------------------------------------------------------- 1 | export const Themes = Object.freeze({ 2 | Auto: 'auto', 3 | Light: 'light', 4 | Dark: 'dark', 5 | }); 6 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /interface/lib/env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains global environment variables. 3 | */ 4 | export class Env { 5 | static browserName = '@@browser_name'; 6 | } 7 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Resources/Icon.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Resources/screenshot-ios.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Resources/screenshot-ios.jpg -------------------------------------------------------------------------------- /interface/devtools/devtool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /interface/lib/options/exportFormats.js: -------------------------------------------------------------------------------- 1 | export const ExportFormats = Object.freeze({ 2 | HeaderString: 'headerstring', 3 | JSON: 'json', 4 | Netscape: 'netscape', 5 | Ask: 'ask', 6 | }); 7 | -------------------------------------------------------------------------------- /interface/lib/browsers.js: -------------------------------------------------------------------------------- 1 | export const Browsers = Object.freeze({ 2 | Any: 'any', 3 | Chrome: 'chrome', 4 | Edge: 'edge', 5 | Firefox: 'firefox', 6 | Opera: 'opera', 7 | Safari: 'safari', 8 | }); 9 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/LargeIcon.imageset/cookie-128-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/LargeIcon.imageset/cookie-128-filled.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Cookie-Editor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moustachauve/cookie-editor/HEAD/safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /site/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://cookie-editor.com/ 5 | 6 | 7 | https://cookie-editor.com/privacy.html 8 | 9 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.2 7 | 8 | 9 | -------------------------------------------------------------------------------- /interface/lib/options/extraInfos.js: -------------------------------------------------------------------------------- 1 | export const ExtraInfos = Object.freeze({ 2 | Nothing: 'nothing', 3 | Value: 'value', 4 | Domain: 'domain', 5 | Path: 'path', 6 | Expiration: 'expiration', 7 | Samesite: 'samesite', 8 | Hostonly: 'hostonly', 9 | Session: 'session', 10 | Secure: 'secure', 11 | Httponly: 'httponly', 12 | }); 13 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Cookie-Editor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "semi": true, 6 | "bracketSpacing": true, 7 | "arrowParens": "avoid", 8 | "trailingComma": "es5", 9 | "bracketSameLine": true, 10 | "printWidth": 80, 11 | "endOfLine": "lf", 12 | "plugins": ["prettier-plugin-organize-imports"] 13 | } -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (App)/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 9 | guard let _ = (scene as? UIWindowScene) else { return } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.ts] 13 | quote_type = single 14 | ij_typescript_use_double_quotes = false 15 | 16 | [*.md] 17 | max_line_length = off 18 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (Extension)/Cookie-Editor.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | @main 5 | class AppDelegate: NSObject, NSApplicationDelegate { 6 | 7 | func applicationDidFinishLaunching(_ notification: Notification) { 8 | // Override point for customization after application launch. 9 | } 10 | 11 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 12 | return true 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (App)/Cookie-Editor.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "cookie-128-filled.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /interface/lib/guid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to handle GUIDs. 3 | */ 4 | export class GUID { 5 | /** 6 | * Generates a v4 UUID using a cryptographically strong random value 7 | * generator. 8 | * https://stackoverflow.com/a/2117523/1244026 9 | * @return {string} A string containing a randomly generated, 36 character 10 | * long v4 UUID. 11 | */ 12 | static get() { 13 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => 14 | ( 15 | c ^ 16 | (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) 17 | ).toString(16) 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | manifest.json 3 | build/* 4 | dist/* 5 | node_modules 6 | 7 | ## User settings 8 | xcuserdata/ 9 | 10 | ## Xcode 8 and earlier 11 | *.xcscmblueprint 12 | *.xccheckout 13 | 14 | ## VisualStudioCode ### 15 | .vscode/* 16 | !.vscode/settings.json 17 | !.vscode/tasks.json 18 | !.vscode/launch.json 19 | !.vscode/extensions.json 20 | !.vscode/*.code-snippets 21 | 22 | # Local History for Visual Studio Code 23 | .history/ 24 | 25 | # Built Visual Studio Code Extensions 26 | *.vsix 27 | 28 | ### VisualStudioCode Patch ### 29 | # Ignore all local history of files 30 | .history 31 | .ionide 32 | .DS_Store 33 | -------------------------------------------------------------------------------- /interface/lib/options/options.js: -------------------------------------------------------------------------------- 1 | import { ExportFormats } from './exportFormats.js'; 2 | import { ExtraInfos } from './extraInfos.js'; 3 | import { Themes } from './themes.js'; 4 | 5 | /** 6 | * The Options class contains all the different options for Cookie-Editor. 7 | */ 8 | export class Options { 9 | /** 10 | * Constructs the options. 11 | */ 12 | constructor() { 13 | this.advancedCookies = false; 14 | this.devtoolsEnabled = true; 15 | this.animationsEnabled = true; 16 | this.exportFormat = ExportFormats.Ask; 17 | this.extraInfo = ExtraInfos.Nothing; 18 | this.theme = Themes.Auto; 19 | this.buttonBarTop = false; 20 | this.adsEnabled = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Compiled output 2 | /dist 3 | /build 4 | /tmp 5 | /out-tsc 6 | /bazel-out 7 | 8 | # Node 9 | /node_modules 10 | npm-debug.log 11 | yarn-error.log 12 | 13 | # IDEs and editors 14 | .idea/ 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | *.sublime-workspace 21 | 22 | # Visual Studio Code 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | .history/* 29 | 30 | # Miscellaneous 31 | /.angular/cache 32 | .sass-cache/ 33 | /connect.lock 34 | /coverage 35 | /libpeerconnection.log 36 | testem.log 37 | /typings 38 | 39 | # System files 40 | .DS_Store 41 | Thumbs.db 42 | 43 | # xcode stuff 44 | /safari 45 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.lint.unknownAtRules": "ignore", 3 | "scss.lint.unknownAtRules": "ignore", 4 | "eslint.enable": true, 5 | "eslint.format.enable": true, 6 | "eslint.validate": ["javascript", "typescript", "html"], 7 | "eslint.useFlatConfig": true, 8 | "eslint.workingDirectories": ["."], 9 | "eslint.options": { 10 | "overrideConfigFile": "eslint.config.mjs" 11 | }, 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "prettier.configPath": ".prettierrc.json", 14 | "editor.formatOnSave": true, 15 | "[javascript]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[typescript]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /site/img/zoom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | @main 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 15 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | import SafariServices 2 | import os.log 3 | 4 | let SFExtensionMessageKey = "message" 5 | 6 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 7 | 8 | func beginRequest(with context: NSExtensionContext) { 9 | let item = context.inputItems[0] as! NSExtensionItem 10 | let message = item.userInfo?[SFExtensionMessageKey] 11 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg) 12 | 13 | let response = NSExtensionItem() 14 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ] 15 | 16 | context.completeRequest(returningItems: [response], completionHandler: nil) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /interface/sidepanel/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | min-width: auto; 5 | } 6 | body { 7 | display: flex; 8 | flex-direction: column; 9 | align-items: stretch; 10 | padding: 0; 11 | margin: 0; 12 | width: 100%; 13 | font-size: 1em; 14 | overflow: hidden; 15 | } 16 | .notransition *, .notransition *::before { 17 | -webkit-transition: none !important; 18 | -moz-transition: none !important; 19 | -o-transition: none !important; 20 | transition: none !important; 21 | } 22 | #cookie-container { 23 | overflow-y: auto; 24 | flex: 1 1 auto; 25 | min-height: initial; 26 | max-height: initial; 27 | min-width: initial; 28 | max-width: initial; 29 | } 30 | 31 | #no-permission > div { 32 | font-size: 0.95em; 33 | padding: 10px; 34 | } 35 | 36 | @media (prefers-color-scheme: dark) { 37 | #cookie-container { 38 | background-color: #202124; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SFSafariWebExtensionConverterVersion 6 | 14.2 7 | UIApplicationSceneManifest 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UIWindowSceneSessionRoleApplication 14 | 15 | 16 | UISceneConfigurationName 17 | Default Configuration 18 | UISceneDelegateClassName 19 | $(PRODUCT_MODULE_NAME).SceneDelegate 20 | UISceneStoryboardFile 21 | Main 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /interface/devtools/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | min-width: auto; 5 | } 6 | 7 | body { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: stretch; 11 | padding: 0; 12 | margin: 0; 13 | width: 100%; 14 | font-size: 1em; 15 | overflow: hidden; 16 | } 17 | .notransition *, .notransition *::before { 18 | -webkit-transition: none !important; 19 | -moz-transition: none !important; 20 | -o-transition: none !important; 21 | transition: none !important; 22 | } 23 | #cookie-container { 24 | overflow-y: auto; 25 | flex: 1 1 auto; 26 | min-height: initial; 27 | max-height: initial; 28 | min-width: initial; 29 | max-width: initial; 30 | } 31 | .button-bar { 32 | display: none; 33 | flex: 0 0 auto; 34 | border-top: 1px solid rgba(0, 0, 0, 0.15); 35 | z-index: 5; 36 | } 37 | 38 | #no-permission > div { 39 | font-size: 0.95em; 40 | padding: 10px; 41 | } 42 | 43 | [data-theme='dark'] #cookie-container { 44 | background-color: #202124; 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookie-editor", 3 | "version": "1.13.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/Moustachauve/cookie-editor.git" 7 | }, 8 | "license": "GPL-3.0-only", 9 | "devDependencies": { 10 | "eslint": "^8.47.0", 11 | "eslint-config-google": "^0.14.0", 12 | "eslint-config-prettier": "^9.0.0", 13 | "eslint-plugin-prettier": "^5.0.0", 14 | "eslint-plugin-simple-import-sort": "^10.0.0", 15 | "grunt": "~1.6.1", 16 | "grunt-contrib-clean": "^2.0.1", 17 | "grunt-contrib-compress": "^2.0.0", 18 | "grunt-contrib-copy": "^1.0.0", 19 | "grunt-exec": "^3.0.0", 20 | "grunt-json-replace": "^0.1.2", 21 | "grunt-remove-logging": "^0.2.0", 22 | "grunt-replace": "^2.0.2", 23 | "prettier": "^3.5.3", 24 | "prettier-eslint": "^16.4.2", 25 | "prettier-plugin-organize-imports": "^4.1.0" 26 | }, 27 | "scripts": { 28 | "lint": "eslint . --config eslint.config.mjs", 29 | "lint:fix": "eslint . --fix --config eslint.config.mjs" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /interface/popup-mobile/dark.css: -------------------------------------------------------------------------------- 1 | 2 | [data-theme='dark'] #cookie-container #searchField { 3 | border: 1px solid var(--secondary-border-color); 4 | } 5 | 6 | [data-theme='dark'] #cookie-container .header { 7 | border: none; 8 | border-top: 1px solid rgba(255, 255, 255, 0.08); 9 | background-color: var(--secondary-surface-color); 10 | } 11 | 12 | [data-theme="dark"] #cookie-container .expando { 13 | background-color: rgba(255, 255, 255, 0.04); 14 | } 15 | [data-theme="dark"] #cookie-container textarea, 16 | [data-theme="dark"] #cookie-container input[type='text'], 17 | [data-theme="dark"] #cookie-container select { 18 | background-color: rgba(255, 255, 255, 0.1); 19 | border-color: rgba(255, 255, 255, 0.1); 20 | } 21 | 22 | [data-theme="dark"] .panel-section-footer-button:focus { 23 | background-color: rgba(255, 255, 255, 0.2); 24 | border: 1px solid rgba(255, 255, 255, 0.2); 25 | color: var(--primary-text-color); 26 | fill: var(--primary-text-color); 27 | } 28 | [data-theme="dark"] .panel-section-footer-button:focus .tooltip { 29 | color: var(--primary-text-color); 30 | } 31 | -------------------------------------------------------------------------------- /interface/lib/eventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Abstract class that allows another class to emit events. 3 | */ 4 | export class EventEmitter { 5 | /** 6 | * Constructs an EventEmitter. 7 | */ 8 | constructor() { 9 | this.queue = {}; 10 | } 11 | 12 | /** 13 | * Emits an event signal to registered listeners. 14 | * @param {string} event Name of the event to signal. 15 | * @param {...any} params Payload to send to the listeners. 16 | */ 17 | emit(event, ...params) { 18 | const queue = this.queue[event]; 19 | 20 | if (typeof queue === 'undefined') { 21 | return; 22 | } 23 | 24 | queue.forEach(function (callback) { 25 | callback(...params); 26 | }); 27 | } 28 | 29 | /** 30 | * Registers a callback to an event to respond to the event signals. 31 | * @param {string} event Name of the event to register to. 32 | * @param {*} callback Callback to register to an event for handling signals. 33 | */ 34 | on(event, callback) { 35 | if (typeof this.queue[event] === 'undefined') { 36 | this.queue[event] = []; 37 | } 38 | 39 | this.queue[event].push(callback); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /interface/lib/jsonFormat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is responsible for parsing and formatting cookies to the 3 | * JSON format. 4 | */ 5 | export class JsonFormat { 6 | /** 7 | * Parses a string of cookie in the JSON format to a cookie object. 8 | * @param {string} cookieString Cookies in the JSON format. 9 | * @return {object} List of Cookies. 10 | */ 11 | static parse(cookieString) { 12 | return JSON.parse(cookieString); 13 | } 14 | 15 | /** 16 | * Formats a list of cookies into a JSON formatted string. 17 | * @param {Cookie[]} cookies Cookies to format. 18 | * @return {string} JSON formatted cookie string. 19 | */ 20 | static format(cookies) { 21 | const exportedCookies = []; 22 | for (const cookieId in cookies) { 23 | if (!Object.prototype.hasOwnProperty.call(cookies, cookieId)) { 24 | continue; 25 | } 26 | const exportedCookie = cookies[cookieId].cookie; 27 | exportedCookie.storeId = null; 28 | if (exportedCookie.sameSite === 'unspecified') { 29 | exportedCookie.sameSite = null; 30 | } 31 | exportedCookies.push(exportedCookie); 32 | } 33 | return JSON.stringify(exportedCookies, null, 4); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /interface/theme/switch.css: -------------------------------------------------------------------------------- 1 | .switch { 2 | position: relative; 3 | display: inline-block; 4 | width: 46px; 5 | height: 22px; 6 | } 7 | .switch input { 8 | opacity: 0; 9 | width: 0; 10 | height: 0; 11 | } 12 | .switch input:focus-visible + .slider { 13 | outline-style: auto; 14 | } 15 | 16 | .slider { 17 | border-radius: 22px; 18 | position: absolute; 19 | cursor: pointer; 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | bottom: 0; 24 | background-color: var(--switch-surface-neutral-color); 25 | -webkit-transition: .4s; 26 | transition: .4s; 27 | outline-offset: 2px; 28 | } 29 | .slider:before { 30 | border-radius: 50%; 31 | position: absolute; 32 | content: ""; 33 | height: 16px; 34 | width: 16px; 35 | left: 4px; 36 | bottom: 3px; 37 | background-color: var(--switch-surface-handle-color); 38 | -webkit-transition: .2s; 39 | transition: .2s; 40 | } 41 | 42 | input:checked + .slider { 43 | background-color: var(--switch-surface-on-color); 44 | } 45 | 46 | input:focus + .slider { 47 | box-shadow: 0 0 1px var(--switch-surface-on-color); 48 | } 49 | 50 | input:checked + .slider:before { 51 | -webkit-transform: translateX(22px); 52 | -ms-transform: translateX(22px); 53 | transform: translateX(22px); 54 | } -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Resources/Style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-user-select: none; 3 | -webkit-user-drag: none; 4 | cursor: default; 5 | } 6 | 7 | :root { 8 | color-scheme: light dark; 9 | 10 | --spacing: 10px; 11 | } 12 | 13 | html { 14 | height: 100%; 15 | } 16 | 17 | body { 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | flex-direction: column; 22 | 23 | gap: var(--spacing); 24 | margin: 0 calc(var(--spacing) * 2); 25 | height: 100%; 26 | 27 | font: -apple-system-short-body; 28 | text-align: center; 29 | } 30 | 31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) { 32 | display: none; 33 | } 34 | 35 | body.platform-ios .platform-mac { 36 | display: none; 37 | } 38 | 39 | body.platform-mac .platform-ios { 40 | display: none; 41 | } 42 | 43 | body.platform-ios .platform-mac { 44 | display: none; 45 | } 46 | 47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) { 48 | display: none; 49 | } 50 | 51 | body.state-on :is(.state-off, .state-unknown) { 52 | display: none; 53 | } 54 | 55 | body.state-off :is(.state-on, .state-unknown) { 56 | display: none; 57 | } 58 | 59 | button { 60 | font-size: 1em; 61 | } 62 | 63 | .limitations { 64 | font-size: 0.8em; 65 | } -------------------------------------------------------------------------------- /interface/lib/ads/ad.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The data class that contains ad informations. 3 | */ 4 | export class Ad { 5 | /** 6 | * Constructs an ad. 7 | * @param {string} id Unique string identifier for a specific ad. 8 | * @param {string} text Text to display for the ad. 9 | * @param {string} tooltip Text to display when a user hover the text. 10 | * @param {string} url Url to redirect the user when they click this ad. 11 | * @param {Browsers[]} supportedBrowsers List of supported browsers to show 12 | * this ad to. 13 | * @param {int} refreshDays How many days to wait before showing the ad again. 14 | * @param {int|null} startDate Timestamp Date before which the ad will not 15 | * show to any user yet. 16 | * @param {int|null} endDate Timestamp Date after which the ad will no 17 | * longer show to any user. 18 | */ 19 | constructor({ 20 | id, 21 | text, 22 | tooltip, 23 | url, 24 | supportedBrowsers, 25 | refreshDays, 26 | startDate, 27 | endDate, 28 | }) { 29 | this.id = id; 30 | this.text = text; 31 | this.tooltip = tooltip; 32 | this.url = url; 33 | this.supportedBrowsers = supportedBrowsers; 34 | this.refreshDays = refreshDays; 35 | this.startDate = startDate; 36 | this.endDate = endDate; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /site/img/edge-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manifest.edge.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Cookie-Editor", 4 | "version": "1.13.0", 5 | "author": "Moustachauve", 6 | "description": "Simple yet powerful Cookie Editor that allow you to quickly create, edit and delete cookies without leaving your tab.", 7 | "offline_enabled": true, 8 | "background": { 9 | "service_worker": "cookie-editor.js", 10 | "type": "module" 11 | }, 12 | "action": { 13 | "default_icon": { 14 | "16": "icons/cookie-16-filled.png", 15 | "19": "icons/cookie-19-filled.png", 16 | "32": "icons/cookie-32-filled.png", 17 | "48": "icons/cookie-48-filled.png" 18 | }, 19 | "default_title": "Cookie-Editor", 20 | "default_popup": "interface/popup/cookie-list.html" 21 | }, 22 | "side_panel": { 23 | "default_path": "interface/sidepanel/cookie-list.html" 24 | }, 25 | "devtools_page": "interface/devtools/devtool.html", 26 | "options_ui": { 27 | "page": "interface/options/options.html" 28 | }, 29 | "permissions": [ 30 | "cookies", 31 | "tabs", 32 | "storage", 33 | "sidePanel" 34 | ], 35 | "host_permissions": [ 36 | "" 37 | ], 38 | "icons": { 39 | "16": "icons/cookie-16-filled.png", 40 | "19": "icons/cookie-19-filled.png", 41 | "32": "icons/cookie-32-filled.png", 42 | "48": "icons/cookie-48-filled.png", 43 | "128": "icons/cookie-128-filled.png" 44 | }, 45 | "incognito": "split" 46 | } -------------------------------------------------------------------------------- /manifest.safari.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Cookie-Editor", 4 | "version": "1.13.0", 5 | "author": "Moustachauve", 6 | "description": "Simple powerful Cookie Editor that allow you to quickly create, edit and delete cookies without leaving your tab", 7 | "offline_enabled": true, 8 | "background": { 9 | "service_worker": "cookie-editor.js", 10 | "type": "module" 11 | }, 12 | "action": { 13 | "default_icon": { 14 | "16": "icons/cookie-16-filled.png", 15 | "19": "icons/cookie-19-filled.png", 16 | "32": "icons/cookie-32-filled.png", 17 | "48": "icons/cookie-48-filled.png" 18 | }, 19 | "default_title": "Cookie-Editor", 20 | "default_popup": "interface/popup/cookie-list.html" 21 | }, 22 | "devtools_page": "interface/devtools/devtool.html", 23 | "options_ui": { 24 | "page": "interface/options/options.html" 25 | }, 26 | "permissions": [ 27 | "cookies", 28 | "tabs", 29 | "storage" 30 | ], 31 | "optional_host_permissions": [ 32 | "" 33 | ], 34 | "icons": { 35 | "16": "icons/cookie-16-filled.png", 36 | "19": "icons/cookie-19-filled.png", 37 | "32": "icons/cookie-32-filled.png", 38 | "48": "icons/cookie-48-filled.png", 39 | "128": "icons/cookie-128-filled.png" 40 | }, 41 | "incognito": "split", 42 | "browser_specific_settings": { 43 | "safari": { 44 | "strict_min_version": "15.4" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /site/img/vivaldi-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifest.opera.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Cookie-Editor", 4 | "version": "1.13.0", 5 | "author": "Moustachauve", 6 | "description": "Simple yet powerful Cookie Editor that allow you to quickly create, edit and delete cookies without leaving your tab.", 7 | "offline_enabled": true, 8 | "background": { 9 | "service_worker": "cookie-editor.js", 10 | "type": "module" 11 | }, 12 | "action": { 13 | "default_icon": { 14 | "16": "icons/cookie-16-filled.png", 15 | "19": "icons/cookie-19-filled.png", 16 | "32": "icons/cookie-32-filled.png", 17 | "48": "icons/cookie-48-filled.png" 18 | }, 19 | "default_title": "Cookie-Editor", 20 | "default_popup": "interface/popup/cookie-list.html" 21 | }, 22 | "side_panel": { 23 | "default_path": "interface/sidepanel/cookie-list.html" 24 | }, 25 | "devtools_page": "interface/devtools/devtool.html", 26 | "options_ui": { 27 | "page": "interface/options/options.html" 28 | }, 29 | "permissions": [ 30 | "cookies", 31 | "tabs", 32 | "storage", 33 | "sidePanel" 34 | ], 35 | "optional_host_permissions": [ 36 | "" 37 | ], 38 | "icons": { 39 | "16": "icons/cookie-16-filled.png", 40 | "19": "icons/cookie-19-filled.png", 41 | "32": "icons/cookie-32-filled.png", 42 | "48": "icons/cookie-48-filled.png", 43 | "128": "icons/cookie-128-filled.png" 44 | }, 45 | "incognito": "split" 46 | } -------------------------------------------------------------------------------- /.github/workflows/npm-grunt.yml: -------------------------------------------------------------------------------- 1 | name: grunt ESLint 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: 7 | branches: ['master'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Cache node modules 21 | id: cache-npm 22 | uses: actions/cache@v3 23 | env: 24 | cache-name: cache-node-modules 25 | with: 26 | # npm cache files are stored in `~/.npm` on Linux/macOS 27 | path: ~/.npm 28 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 29 | restore-keys: | 30 | ${{ runner.os }}-build-${{ env.cache-name }}- 31 | ${{ runner.os }}-build- 32 | ${{ runner.os }}- 33 | 34 | - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} 35 | name: List the state of node modules 36 | continue-on-error: true 37 | run: npm list 38 | 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | 44 | - name: Install dependencies 45 | run: npm install 46 | 47 | - name: Test 48 | run: grunt exec:lint 49 | -------------------------------------------------------------------------------- /manifest.chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Cookie-Editor", 4 | "version": "1.13.0", 5 | "author": "Moustachauve", 6 | "description": "Simple yet powerful Cookie Editor that allow you to quickly create, edit and delete cookies without leaving your tab.", 7 | "offline_enabled": true, 8 | "minimum_chrome_version": "102", 9 | "background": { 10 | "service_worker": "cookie-editor.js", 11 | "type": "module" 12 | }, 13 | "action": { 14 | "default_icon": { 15 | "16": "icons/cookie-16-filled.png", 16 | "19": "icons/cookie-19-filled.png", 17 | "32": "icons/cookie-32-filled.png", 18 | "48": "icons/cookie-48-filled.png" 19 | }, 20 | "default_title": "Cookie-Editor", 21 | "default_popup": "interface/popup/cookie-list.html" 22 | }, 23 | "side_panel": { 24 | "default_path": "interface/sidepanel/cookie-list.html" 25 | }, 26 | "devtools_page": "interface/devtools/devtool.html", 27 | "options_ui": { 28 | "page": "interface/options/options.html" 29 | }, 30 | "permissions": [ 31 | "cookies", 32 | "tabs", 33 | "storage", 34 | "sidePanel" 35 | ], 36 | "optional_host_permissions": [ 37 | "" 38 | ], 39 | "icons": { 40 | "16": "icons/cookie-16-filled.png", 41 | "19": "icons/cookie-19-filled.png", 42 | "32": "icons/cookie-32-filled.png", 43 | "48": "icons/cookie-48-filled.png", 44 | "128": "icons/cookie-128-filled.png" 45 | }, 46 | "incognito": "split" 47 | } -------------------------------------------------------------------------------- /site/img/opera-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Base.lproj/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Cookie-Editor Icon 14 |

You can turn on Cookie-Editor’s Safari extension in Settings.

15 |

16 | Here's how to access Cookie-Editor in Safari:
17 | How to open Cookie-Editor 18 |

19 |

You can turn on Cookie-Editor’s extension in Safari Extensions preferences.

20 |

Cookie-Editor’s extension is currently on. You can turn it off in Safari Extensions preferences.

21 |

Cookie-Editor’s extension is currently off. You can turn it on in Safari Extensions preferences.

22 | 23 |

24 | Due to some limitation on the Safari browser, not all cookies can be displayed. 25 | For example, Safari can't display cookies with "HTTPOnly" set to true. 26 |

27 | 28 | 29 | -------------------------------------------------------------------------------- /interface/devtools/devtools.js: -------------------------------------------------------------------------------- 1 | import { BrowserDetector } from '../lib/browserDetector.js'; 2 | import { GenericStorageHandler } from '../lib/genericStorageHandler.js'; 3 | import { OptionsHandler } from '../lib/optionsHandler.js'; 4 | 5 | (async function () { 6 | ('use strict'); 7 | const browserDetector = new BrowserDetector(); 8 | const storageHandler = new GenericStorageHandler(browserDetector); 9 | const optionHandler = new OptionsHandler(browserDetector, storageHandler); 10 | 11 | await optionHandler.loadOptions(); 12 | optionHandler.on('optionsChanged', onOptionsChanged); 13 | handleDevtools(); 14 | 15 | /** 16 | * Creates the devtools panel. 17 | */ 18 | function createDevtools() { 19 | browserDetector 20 | .getApi() 21 | .devtools.panels.create( 22 | 'Cookie-Editor', 23 | '/icons/cookie-filled-small.svg', 24 | '/interface/devtools/cookie-list.html', 25 | function (panel) {} 26 | ); 27 | } 28 | 29 | /** 30 | * Shows or hides the devtools depending on the options. 31 | */ 32 | function handleDevtools() { 33 | console.log('devtools enabled?', optionHandler.getDevtoolsEnabled()); 34 | if (optionHandler.getDevtoolsEnabled()) { 35 | createDevtools(); 36 | } 37 | } 38 | 39 | /** 40 | * Handles the changes required to the devtools when the options are changed 41 | * by an external source. 42 | * @param {Option} oldOptions the options before changes. 43 | */ 44 | function onOptionsChanged(oldOptions) { 45 | if (oldOptions.devtoolsEnabled != optionHandler.getDevtoolsEnabled()) { 46 | handleDevtools(); 47 | } 48 | } 49 | })(); 50 | -------------------------------------------------------------------------------- /interface/lib/themeHandler.js: -------------------------------------------------------------------------------- 1 | import { Themes } from './options/themes.js'; 2 | 3 | /** 4 | * Class used to handle the theme of a page. 5 | */ 6 | export class ThemeHandler { 7 | /** 8 | * Constructs the ThemeHandler. 9 | * @param {optionHandler} optionHandler 10 | */ 11 | constructor(optionHandler) { 12 | this.optionHandler = optionHandler; 13 | optionHandler.on('optionsChanged', this.onOptionsChanged); 14 | window 15 | .matchMedia('(prefers-color-scheme: dark)') 16 | .addEventListener('change', event => { 17 | console.log('theme changed!'); 18 | this.updateTheme(); 19 | }); 20 | } 21 | 22 | /** 23 | * Handles which theme the page will be rendered with. 24 | */ 25 | updateTheme() { 26 | const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); 27 | const selectedTheme = this.optionHandler.getTheme(); 28 | switch (selectedTheme) { 29 | case Themes.Light: 30 | case Themes.Dark: 31 | document.body.dataset.theme = selectedTheme; 32 | break; 33 | default: 34 | if (prefersDarkScheme.matches) { 35 | document.body.dataset.theme = 'dark'; 36 | } else { 37 | document.body.dataset.theme = 'light'; 38 | } 39 | break; 40 | } 41 | } 42 | 43 | /** 44 | * Handles the changes required to the interface when the options are changed 45 | * by an external source. 46 | * @param {Option} oldOptions the options before changes. 47 | */ 48 | onOptionsChanged = oldOptions => { 49 | if (oldOptions.theme != this.optionHandler.getTheme()) { 50 | this.updateTheme(); 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /interface/theme/light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-surface-color: #ffffff; 3 | --primary-border-color: #c5c5c5; 4 | --primary-outline-color: #444; 5 | --primary-text-color: #000000; 6 | --primary-link-color: #0d63d3; 7 | --primary-link-hover-color: #85b9fc; 8 | --primary-danger-color: #cf000f; 9 | --primary-safe-color: #1e824c; 10 | --secondary-danger-color: #970707; 11 | 12 | --secondary-surface-color: #eaeaea; 13 | --secondary-border-color: #aaaaaa; 14 | --secondary-text-color: #777777; 15 | --secondary-text-onsurface-color: var(--primary-text-color); 16 | 17 | --changed-surface-color: #fdfb8e; 18 | --success-surface-color: #9ee7a8; 19 | 20 | --ad-surface-color: #b1d7f7; 21 | --ad-text-color: #214357; 22 | --ad-link-color: #195179; 23 | 24 | --search-surface-color: #f5f5f5; 25 | --search-surface-hover-color: #ffffec; 26 | --search-surface-content-color: gold; 27 | 28 | --button-primary-surface-color: #1b98f1; 29 | --button-primary-text-color: #ffffff; 30 | --button-danger-surface-color: #f800002b; 31 | 32 | --notification-surface-color: rgba(0, 0, 0, 0.8); 33 | --notification-text-color: #fafafa; 34 | --notification-dismiss-color: #656565; 35 | --notification-dismiss-hover-parent-color: #aaa; 36 | --notification-dismiss-hover-color: var(--notification-text-color); 37 | 38 | --menu-surface-color: var(--primary-surface-color); 39 | --menu-surface-hover-color: var(--secondary-surface-color); 40 | --menu-border-color: var(--primary-border-color); 41 | 42 | --switch-surface-handle-color: var(--primary-surface-color); 43 | --switch-surface-on-color: #2196F3; 44 | --switch-surface-neutral-color: var(--primary-border-color); 45 | 46 | --notice-danger-surface-color: #f8000011; 47 | } -------------------------------------------------------------------------------- /interface/theme/dark.css: -------------------------------------------------------------------------------- 1 | [data-theme='dark'] { 2 | color-scheme: dark; 3 | 4 | --primary-surface-color: #2c2e31; 5 | --primary-border-color: #696969; 6 | --primary-outline-color: #4786ec; 7 | --primary-text-color: #fafafa; 8 | --primary-link-color: #8fc9eb; 9 | --primary-link-hover-color: #d5ecf8; 10 | --primary-danger-color: #f71323; 11 | --primary-safe-color: #1db464; 12 | 13 | --secondary-surface-color: #303134; 14 | --secondary-border-color: #3f4146; 15 | --secondary-text-color: #aaaaaa; 16 | --secondary-text-onsurface-color: #eaeaea; 17 | --secondary-danger-color: #f8abb0; 18 | 19 | --changed-surface-color: #3a391a; 20 | --success-surface-color: #15531d; 21 | 22 | --ad-surface-color: #122738; 23 | --ad-text-color: #7ac2ec; 24 | --ad-link-color: var(--primary-link-color); 25 | 26 | --search-surface-color: #383b41; 27 | --search-surface-hover-color: #494b50; 28 | --search-surface-content-color: #665f37; 29 | 30 | --button-primary-surface-color: #1b98f1; 31 | --button-primary-text-color: #ffffff; 32 | --button-danger-surface-color: #f800003f; 33 | 34 | --notification-surface-color: rgba(0, 0, 0, 0.8); 35 | --notification-text-color: #fafafa; 36 | --notification-dismiss-color: #656565; 37 | --notification-dismiss-hover-parent-color: #aaa; 38 | --notification-dismiss-hover-color: var(--notification-text-color); 39 | 40 | --menu-surface-color: #45474d; 41 | --menu-surface-hover-color: #373b44; 42 | --menu-border-color: rgba(255, 255, 255, 0.2); 43 | 44 | --switch-surface-handle-color: var(--secondary-surface-color); 45 | --switch-surface-on-color: #1b7bca; 46 | --switch-surface-neutral-color: var(--primary-border-color); 47 | 48 | --notice-danger-surface-color: #f8000015; 49 | } 50 | -------------------------------------------------------------------------------- /interface/popup/dark.css: -------------------------------------------------------------------------------- 1 | 2 | [data-theme='dark'] #cookie-container #searchField { 3 | border: 1px solid var(--secondary-border-color); 4 | } 5 | 6 | [data-theme='dark'] #cookie-container .header { 7 | border: none; 8 | border-top: 1px solid rgba(255, 255, 255, 0.08); 9 | background-color: var(--secondary-surface-color); 10 | } 11 | 12 | [data-theme="dark"] #cookie-container .expando { 13 | background-color: rgba(255, 255, 255, 0.04); 14 | } 15 | [data-theme="dark"] #cookie-container textarea, 16 | [data-theme="dark"] #cookie-container input[type='text'], 17 | [data-theme="dark"] #cookie-container select { 18 | background-color: rgba(255, 255, 255, 0.1); 19 | border-color: rgba(255, 255, 255, 0.1); 20 | } 21 | 22 | [data-theme="dark"] #cookie-container textarea:hover, 23 | [data-theme="dark"] #cookie-container input:not([disabled])[type='text']:hover { 24 | border-color: rgb(82, 130, 192); 25 | box-shadow: 1px 1px 5px rgba(150, 150, 150, 0.4); 26 | } 27 | 28 | [data-theme="dark"] .panel-section-footer-button:hover, 29 | [data-theme="dark"] .panel-section-footer-button:focus { 30 | background-color: rgba(255, 255, 255, 0.2); 31 | border: 1px solid rgba(255, 255, 255, 0.2); 32 | color: var(--primary-text-color); 33 | fill: var(--primary-text-color); 34 | } 35 | [data-theme="dark"] .panel-section-footer-button .tooltip { 36 | color: var(--primary-text-color); 37 | } 38 | [data-theme="dark"] .panel-section-footer-button:hover .tooltip, 39 | [data-theme="dark"] .panel-section-footer-button:focus .tooltip { 40 | color: var(--primary-text-color); 41 | } 42 | 43 | [data-theme="dark"] #export-menu button:hover { 44 | border-color: rgba(255, 255, 255, 0.3); 45 | background-color: rgba(255, 255, 255, 0.15); 46 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1); 47 | } 48 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import google from 'eslint-config-google'; 3 | import prettierConfig from 'eslint-config-prettier'; 4 | import prettier from 'eslint-plugin-prettier'; 5 | import simpleImportSort from 'eslint-plugin-simple-import-sort'; 6 | import globals from 'globals'; 7 | 8 | export default [ 9 | // Global ignores 10 | { 11 | ignores: [ 12 | 'dist/**', 13 | 'build/**', 14 | 'node_modules/**', 15 | '.vscode/extensions.js', 16 | 'safari/**', 17 | ], 18 | }, 19 | 20 | // Base configuration for JS/MJS files 21 | js.configs.recommended, 22 | 23 | // Google's style guide 24 | google, 25 | 26 | // Main custom configuration 27 | { 28 | files: ['**/*.{js,mjs}'], 29 | plugins: { 30 | prettier, 31 | 'simple-import-sort': simpleImportSort, 32 | }, 33 | languageOptions: { 34 | ecmaVersion: 'latest', 35 | sourceType: 'module', 36 | globals: { 37 | ...globals.browser, 38 | ...globals.webextensions, 39 | }, 40 | }, 41 | rules: { 42 | 'prettier/prettier': 'error', 43 | 'simple-import-sort/imports': 'error', 44 | 'simple-import-sort/exports': 'error', 45 | }, 46 | }, 47 | 48 | // Override for Node.js configuration files 49 | { 50 | files: ['eslint.config.mjs'], 51 | languageOptions: { 52 | globals: { 53 | ...globals.node, 54 | }, 55 | }, 56 | }, 57 | { 58 | files: ['Gruntfile.js'], 59 | languageOptions: { 60 | sourceType: 'commonjs', 61 | globals: { 62 | ...globals.node, 63 | }, 64 | }, 65 | }, 66 | 67 | // Prettier config must be last 68 | // This turns off all rules that are unnecessary or might conflict with Prettier. 69 | prettierConfig, 70 | ]; 71 | -------------------------------------------------------------------------------- /interface/lib/headerstringFormat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is responsible for parsing and formatting cookies to the 3 | * Header string format. 4 | */ 5 | export class HeaderstringFormat { 6 | /** 7 | * Parses a string of cookie in the JSON format to a cookie object. 8 | * @param {string} cookieString Cookies in the JSON format. 9 | * @return {object} List of Cookies. 10 | */ 11 | static parse(cookieString) { 12 | const cookies = []; 13 | const rawCookies = cookieString.split(';'); 14 | for (let rawCookie of rawCookies) { 15 | rawCookie = rawCookie.trim(); 16 | if (!rawCookie.length) { 17 | continue; 18 | } 19 | const eqPos = rawCookie.indexOf('='); 20 | if (eqPos === -1) { 21 | console.log('invalid cookie: ', rawCookie); 22 | continue; 23 | } 24 | cookies.push({ 25 | name: rawCookie.substring(0, eqPos), 26 | value: rawCookie.substring(eqPos + 1), 27 | }); 28 | } 29 | 30 | if (cookies.length === 0) { 31 | throw new Error('No cookies found.'); 32 | } 33 | 34 | return cookies; 35 | } 36 | 37 | /** 38 | * Formats a list of cookies into a Header string formatted string. 39 | * @param {Cookie[]} cookies Cookies to format. 40 | * @return {string} Header string formatted cookie string. 41 | */ 42 | static format(cookies) { 43 | const exportedCookies = []; 44 | for (const cookieId in cookies) { 45 | if (!Object.prototype.hasOwnProperty.call(cookies, cookieId)) { 46 | continue; 47 | } 48 | const exportedCookie = cookies[cookieId].cookie; 49 | const name = exportedCookie.name; 50 | const value = exportedCookie.value; 51 | exportedCookies.push(`${name}=${value}`); 52 | } 53 | return exportedCookies.join(';'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "1024x1024", 5 | "idiom" : "universal", 6 | "filename" : "universal-icon-1024@1x.png", 7 | "platform" : "ios" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "mac-icon-16@1x.png", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "size" : "16x16", 17 | "idiom" : "mac", 18 | "filename" : "mac-icon-16@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "mac-icon-32@1x.png", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "size" : "32x32", 29 | "idiom" : "mac", 30 | "filename" : "mac-icon-32@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "mac-icon-128@1x.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "128x128", 41 | "idiom" : "mac", 42 | "filename" : "mac-icon-128@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "mac-icon-256@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "256x256", 53 | "idiom" : "mac", 54 | "filename" : "mac-icon-256@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "mac-icon-512@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "512x512", 65 | "idiom" : "mac", 66 | "filename" : "mac-icon-512@2x.png", 67 | "scale" : "2x" 68 | } 69 | ], 70 | "info" : { 71 | "version" : 1, 72 | "author" : "xcode" 73 | } 74 | } -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/Resources/Script.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /** 3 | * Shows the window in the main app that serves to sideload the extension. 4 | * @param {object} platform 5 | * @param {boolean} enabled 6 | * @param {boolean} useSettingsInsteadOfPreferences 7 | */ 8 | function show(platform, enabled, useSettingsInsteadOfPreferences) { 9 | document.body.classList.add(`platform-${platform}`); 10 | 11 | if (useSettingsInsteadOfPreferences) { 12 | document.getElementsByClassName('platform-mac state-on')[0].innerText = 13 | 'Cookie-Editor’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.'; 14 | document.getElementsByClassName('platform-mac state-off')[0].innerText = 15 | 'Cookie-Editor’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.'; 16 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = 17 | 'You can turn on Cookie-Editor’s extension in the Extensions section of Safari Settings.'; 18 | document.getElementsByClassName( 19 | 'platform-mac open-preferences', 20 | )[0].innerText = 'Quit and Open Safari Settings…'; 21 | } 22 | 23 | if (typeof enabled === 'boolean') { 24 | document.body.classList.toggle(`state-on`, enabled); 25 | document.body.classList.toggle(`state-off`, !enabled); 26 | } else { 27 | document.body.classList.remove(`state-on`); 28 | document.body.classList.remove(`state-off`); 29 | } 30 | } 31 | 32 | /** 33 | * Opens Safari's preference window. 34 | */ 35 | function openPreferences() { 36 | // eslint-disable-next-line no-undef 37 | webkit.messageHandlers.controller.postMessage('open-preferences'); 38 | } 39 | 40 | document 41 | .querySelector('button.open-preferences') 42 | .addEventListener('click', openPreferences); 43 | -------------------------------------------------------------------------------- /interface/lib/genericStorageHandler.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './eventEmitter.js'; 2 | 3 | /** 4 | * Abstract class used to implement basic common Storage API handling. 5 | */ 6 | export class GenericStorageHandler extends EventEmitter { 7 | /** 8 | * Constructs a GenericStorageHandler. 9 | * @param {BrowserDetector} browserDetector 10 | */ 11 | constructor(browserDetector) { 12 | super(); 13 | this.browserDetector = browserDetector; 14 | } 15 | 16 | /** 17 | * Gets a value from LocalStorage. 18 | * @param {string} key Key to identify the value in the LocalStorage. 19 | * @return {Promise} 20 | */ 21 | async getLocal(key) { 22 | const self = this; 23 | let promise; 24 | if (this.browserDetector.supportsPromises()) { 25 | promise = this.browserDetector.getApi().storage.local.get([key]); 26 | } else { 27 | promise = new Promise((resolve, reject) => { 28 | self.browserDetector.getApi().storage.local.get([key], data => { 29 | const error = self.browserDetector.getApi().runtime.lastError; 30 | if (error) { 31 | reject(error); 32 | } 33 | resolve(data ?? null); 34 | }); 35 | }); 36 | } 37 | 38 | return promise.then(data => { 39 | return data[key] ?? null; 40 | }); 41 | } 42 | 43 | /** 44 | * Sets a value in the LocalStorage. 45 | * @param {string} key Key to identify the value in the LocalStorage. 46 | * @param {any} data Data to store in the LocalStorage 47 | * @return {Promise} 48 | */ 49 | async setLocal(key, data) { 50 | const self = this; 51 | const dataObj = {}; 52 | dataObj[key] = data; 53 | 54 | if (this.browserDetector.supportsPromises()) { 55 | return this.browserDetector.getApi().storage.local.set(dataObj); 56 | } else { 57 | return new Promise((resolve, reject) => { 58 | this.browserDetector.getApi().storage.local.set(dataObj, () => { 59 | const error = self.browserDetector.getApi().runtime.lastError; 60 | if (error) { 61 | reject(error); 62 | } 63 | resolve(); 64 | }); 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /manifest.firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Cookie-Editor", 4 | "version": "1.13.0", 5 | "author": "Moustachauve", 6 | "description": "Simple yet powerful Cookie Editor that allow you to quickly create, edit and delete cookies without leaving your tab.", 7 | "background": { 8 | "scripts": [ 9 | "interface/lib/env.js", 10 | "interface/lib/browserDetector.js", 11 | "interface/lib/permissionHandler.js", 12 | "cookie-editor.js" 13 | ], 14 | "type": "module" 15 | }, 16 | "action": { 17 | "default_icon": { 18 | "16": "icons/cookie-filled-small.svg", 19 | "32": "icons/cookie-filled-small.svg", 20 | "48": "icons/cookie-filled.svg", 21 | "128": "icons/cookie-filled.svg" 22 | }, 23 | "theme_icons": [ 24 | { 25 | "light": "icons/cookie-light-small.svg", 26 | "dark": "icons/cookie-small.svg", 27 | "size": 16 28 | }, 29 | { 30 | "light": "icons/cookie-light-small.svg", 31 | "dark": "icons/cookie-small.svg", 32 | "size": 32 33 | }, 34 | { 35 | "light": "icons/cookie-light.svg", 36 | "dark": "icons/cookie.svg", 37 | "size": 128 38 | } 39 | ], 40 | "default_title": "Cookie-Editor", 41 | "default_popup": "interface/popup/cookie-list.html" 42 | }, 43 | "sidebar_action": { 44 | "default_icon": { 45 | "16": "icons/cookie-filled-small.svg", 46 | "32": "icons/cookie-filled-small.svg" 47 | }, 48 | "default_title": "Cookie-Editor", 49 | "default_panel": "interface/sidepanel/cookie-list.html" 50 | }, 51 | "devtools_page": "interface/devtools/devtool.html", 52 | "options_ui": { 53 | "page": "interface/options/options.html" 54 | }, 55 | "permissions": [ 56 | "cookies", 57 | "tabs", 58 | "storage" 59 | ], 60 | "host_permissions": [ 61 | "" 62 | ], 63 | "icons": { 64 | "16": "icons/cookie-filled-small.svg", 65 | "19": "icons/cookie-filled-small.svg", 66 | "32": "icons/cookie-filled-small.svg", 67 | "48": "icons/cookie-filled.svg", 68 | "128": "icons/cookie-filled.svg" 69 | }, 70 | "browser_specific_settings": { 71 | "gecko": { 72 | "id": "{c3c10168-4186-445c-9c5b-63f12b8e2c87}", 73 | "strict_min_version": "112.0" 74 | }, 75 | "gecko_android": { 76 | "id": "cookie-editor@cookie-editor.com", 77 | "strict_min_version": "112.0" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /interface/devtools/permissionHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles tasks related to the permission API. 3 | * 4 | * interface/lib/permissionHandler.js needs to be kept in sync to the functions in this file 5 | * TODO: Implement this in a way to only override specific functions. 6 | */ 7 | export class PermissionHandler { 8 | /** 9 | * Constructs the permission handler. 10 | * @param {BrowserDetector} browserDetector 11 | */ 12 | constructor(browserDetector) { 13 | this.browserDetector = browserDetector; 14 | } 15 | 16 | /** 17 | * Check if it is possible for a website to have permissions. for example, 18 | * on firefox, it is impossible to check for permission on internal pages 19 | * (for example, about:[...]). 20 | * @param {string} url Url to check. 21 | * @return {boolean} true if it is possible to check, otherwise false. 22 | */ 23 | canHavePermissions(url) { 24 | if (url.indexOf('about:') === 0 || url.indexOf('edge:') === 0) { 25 | return false; 26 | } 27 | return true; 28 | } 29 | 30 | /** 31 | * Checks if the extension has permissions to manipulate cookies for a 32 | * specific url. 33 | * @param {string} url Url to check 34 | * @return {Promise} 35 | */ 36 | async checkPermissions(url) { 37 | return await this.sendMessage('permissionsContains', url); 38 | } 39 | 40 | /** 41 | * Requests permissions to manipulate cookies for a specific url. 42 | * @param {string} url Url to check 43 | * @return {Promise} 44 | */ 45 | async requestPermission(url) { 46 | return await this.sendMessage('permissionsRequest', url); 47 | } 48 | 49 | /** 50 | * Sends a message to the background script. 51 | * @param {string} type Type of the message. 52 | * @param {object} params Payload of the message. 53 | * @return {Promise} 54 | */ 55 | sendMessage(type, params) { 56 | const self = this; 57 | if (this.browserDetector.supportsPromises()) { 58 | return this.browserDetector 59 | .getApi() 60 | .runtime.sendMessage({ type: type, params: params }); 61 | } else { 62 | return new Promise(function (resolve) { 63 | self.browserDetector 64 | .getApi() 65 | .runtime.sendMessage({ type: type, params: params }, resolve); 66 | }); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/Shared (App)/ViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | import WebKit 3 | 4 | #if os(iOS) 5 | import UIKit 6 | typealias PlatformViewController = UIViewController 7 | #elseif os(macOS) 8 | import Cocoa 9 | import SafariServices 10 | typealias PlatformViewController = NSViewController 11 | #endif 12 | 13 | let extensionBundleIdentifier = "ca.cgagnier.cookie-editor.Extension" 14 | 15 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler { 16 | 17 | @IBOutlet var webView: WKWebView! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.webView.navigationDelegate = self 23 | 24 | #if os(iOS) 25 | self.webView.scrollView.isScrollEnabled = false 26 | #endif 27 | 28 | self.webView.configuration.userContentController.add(self, name: "controller") 29 | 30 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) 31 | } 32 | 33 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 34 | #if os(iOS) 35 | webView.evaluateJavaScript("show('ios')") 36 | #elseif os(macOS) 37 | webView.evaluateJavaScript("show('mac')") 38 | 39 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in 40 | guard let state = state, error == nil else { 41 | // Insert code to inform the user that something went wrong. 42 | return 43 | } 44 | 45 | DispatchQueue.main.async { 46 | if #available(macOS 13, *) { 47 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)") 48 | } else { 49 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)") 50 | } 51 | } 52 | } 53 | #endif 54 | } 55 | 56 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 57 | #if os(macOS) 58 | if (message.body as! String != "open-preferences") { 59 | return; 60 | } 61 | 62 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in 63 | guard error == nil else { 64 | // Insert code to inform the user that something went wrong. 65 | return 66 | } 67 | 68 | DispatchQueue.main.async { 69 | NSApplication.shared.terminate(nil) 70 | } 71 | } 72 | #endif 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (App)/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/iOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /site/img/github-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /interface/lib/netscapeFormat.js: -------------------------------------------------------------------------------- 1 | const httpOnlyPrefix = '#HttpOnly_'; 2 | 3 | /** 4 | * This class is responsible for parsing and formatting cookies to the 5 | * Netscape format. 6 | */ 7 | export class NetscapeFormat { 8 | /** 9 | * Parses a string of cookie in the Netscape format to a cookie object. 10 | * @param {string} cookieString Cookies in the Netscape format. 11 | * @return {object} List of Cookies. 12 | */ 13 | static parse(cookieString) { 14 | const cookies = []; 15 | const lines = cookieString.split('\n'); 16 | for (let line of lines) { 17 | line = line.trim(); 18 | if (!line.length) { 19 | continue; 20 | } 21 | const isHttpOnly = line.startsWith(httpOnlyPrefix); 22 | if (isHttpOnly) { 23 | line = line.substring(httpOnlyPrefix.length); 24 | } 25 | // Skip comments 26 | if (line[0] == '#') { 27 | continue; 28 | } 29 | 30 | const elements = line.split('\t'); 31 | if (elements.length != 7) { 32 | throw new Error('Invalid netscape format'); 33 | } 34 | cookies.push({ 35 | domain: elements[0], 36 | hostOnly: elements[1].toLowerCase() === 'false', 37 | path: elements[2], 38 | secure: elements[3].toLowerCase() === 'true', 39 | expiration: elements[4], 40 | name: elements[5], 41 | value: elements[6], 42 | httpOnly: isHttpOnly, 43 | }); 44 | } 45 | return cookies; 46 | } 47 | 48 | /** 49 | * Formats a list of cookies into a Netscape formatted string. 50 | * @param {Cookie[]} cookies Cookies to format. 51 | * @return {string} Netscape formatted cookie string. 52 | */ 53 | static format(cookies) { 54 | let netscapeCookies = '# Netscape HTTP Cookie File'; 55 | netscapeCookies += '\n# http://curl.haxx.se/rfc/cookie_spec.html'; 56 | netscapeCookies += '\n# This file was generated by Cookie-Editor'; 57 | for (const cookieId in cookies) { 58 | if (!Object.prototype.hasOwnProperty.call(cookies, cookieId)) { 59 | continue; 60 | } 61 | const cookie = cookies[cookieId].cookie; 62 | const secure = cookie.secure.toString().toUpperCase(); 63 | let expiration = 0; 64 | 65 | if (cookie.session) { 66 | // Create sessions with a 1 day TTL to avoid the cookie being 67 | // discarded when imported back. This is a compromise due to the 68 | // Netscape format. It is short enough but not too short. 69 | expiration = Math.trunc( 70 | new Date(Date.now() + 86400 * 1000).getTime() / 1000 71 | ); 72 | } else if (!cookie.session && !!cookie.expirationDate) { 73 | expiration = Math.trunc(cookie.expirationDate); 74 | } 75 | const includesSubdomain = cookie.hostOnly ? 'FALSE' : 'TRUE'; 76 | 77 | const httpOnly = cookie.httpOnly ? httpOnlyPrefix : ''; 78 | 79 | netscapeCookies += 80 | `\n${httpOnly}${cookie.domain} ${includesSubdomain} ` + 81 | `${cookie.path} ${secure} ${expiration} ${cookie.name}` + 82 | ` ${cookie.value}`; 83 | } 84 | return netscapeCookies; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /interface/options/style.css: -------------------------------------------------------------------------------- 1 | @import url('../theme/light.css'); 2 | @import url('../theme/dark.css'); 3 | 4 | body { 5 | font-family: 'Segoe UI', Tahoma, sans-serif; 6 | font-size: 100%; 7 | background-color: var(--primary-surface-color); 8 | color: var(--primary-text-color); 9 | } 10 | 11 | body * { 12 | box-sizing: border-box; 13 | text-align: start; 14 | } 15 | .notransition *, .notransition *::before { 16 | -webkit-transition: none !important; 17 | -moz-transition: none !important; 18 | -o-transition: none !important; 19 | transition: none !important; 20 | } 21 | 22 | svg.icon { 23 | height: 1em; 24 | width: 1em; 25 | pointer-events: none; 26 | } 27 | 28 | a { 29 | color: var(--primary-link-color); 30 | } 31 | 32 | a:hover { 33 | color: var(--primary-link-hover-color); 34 | } 35 | 36 | .container { 37 | width: 100%; 38 | max-width: 600px; 39 | margin: 0 auto; 40 | } 41 | 42 | .container > fieldset { 43 | margin: 15px; 44 | padding: 5px 15px; 45 | border: 1px solid var(--primary-border-color); 46 | border-radius: 4px; 47 | } 48 | .container > fieldset legend { 49 | padding: 0 5px; 50 | font-size: 1.25em; 51 | } 52 | 53 | /* == Input styling == */ 54 | 55 | .input-container { 56 | min-height: 2em; 57 | margin-top: 5px; 58 | } 59 | 60 | .input-container input, 61 | .input-container select, 62 | .input-container .switch, 63 | .input-container button, 64 | .input-container .button-group { 65 | float: right; 66 | } 67 | .input-container select { 68 | min-width: 120px; 69 | background-color: var(--primary-surface-color); 70 | border: 1px solid var(--primary-border-color); 71 | border-radius: 4px; 72 | color: var(--primary-text-color); 73 | padding: 2px 5px; 74 | } 75 | 76 | .input-container button { 77 | margin: 0 5px; 78 | padding: 4px 8px; 79 | background-color: var(--menu-surface-color); 80 | border: 1px solid var(--primary-border-color); 81 | border-radius: 4px; 82 | cursor: pointer; 83 | } 84 | 85 | .input-container button:hover { 86 | background-color: var(--menu-surface-hover-color); 87 | } 88 | 89 | .input-container button.danger { 90 | background-color: var(--button-danger-surface-color); 91 | border: 1px solid var(--button-danger-surface-color); 92 | color: var(--primary-text-color); 93 | } 94 | 95 | .input-container button.danger:hover { 96 | background-color: var(--primary-danger-color); 97 | color: var(--button-primary-text-color); 98 | } 99 | 100 | .hint { 101 | clear: both; 102 | color: var(--secondary-text-color); 103 | font-size: 0.8em; 104 | padding: 1em; 105 | padding-top: 4px; 106 | } 107 | 108 | .notice { 109 | font-size: 0.9em; 110 | padding: 8px; 111 | margin: 5px 0; 112 | margin-bottom: 15px; 113 | background-color: var(--secondary-surface-color); 114 | border-radius: 4px; 115 | } 116 | 117 | .notice.danger { 118 | color: var(--secondary-text-color); 119 | background-color: var(--notice-danger-surface-color); 120 | } 121 | 122 | .danger { 123 | color: var(--secondary-danger-color); 124 | } 125 | .danger svg { 126 | fill: var(--secondary-danger-color); 127 | font-size: 1.2em; 128 | } 129 | 130 | .disclaimer { 131 | font-size: 0.8em; 132 | color: var(--secondary-text-color); 133 | padding-top: 5px; 134 | } 135 | 136 | .hidden { 137 | display: none; 138 | } 139 | -------------------------------------------------------------------------------- /interface/lib/browserDetector.js: -------------------------------------------------------------------------------- 1 | import { Browsers } from './browsers.js'; 2 | import { Env } from './env.js'; 3 | 4 | /** 5 | * Detects information about the browser being used. 6 | */ 7 | export class BrowserDetector { 8 | /** 9 | * Constructs the BrowserDetector. 10 | */ 11 | constructor() { 12 | console.log('constructing a browserDetector'); 13 | this.namespace = chrome || window.browser || window.chrome; 14 | this.supportPromises = false; 15 | this.supportSidePanel = false; 16 | 17 | try { 18 | this.supportPromises = 19 | this.namespace.runtime.getPlatformInfo() instanceof Promise; 20 | console.info('Promises support: ', this.supportPromises); 21 | } catch (e) { 22 | /* empty */ 23 | } 24 | 25 | try { 26 | this.supportSidePanel = typeof this.getApi().sidePanel !== 'undefined'; 27 | console.info('SidePanel support: ', this.supportSidePanel); 28 | } catch (e) { 29 | /* empty */ 30 | } 31 | 32 | if (Env.browserName === '@@browser_name') { 33 | Env.browserName = Browsers.Chrome; 34 | console.warn( 35 | 'undefined browser name, using ' + Env.browserName + ' as fallback' 36 | ); 37 | } 38 | 39 | console.log(Env.browserName); 40 | } 41 | 42 | /** 43 | * Get the main API container specific to the current browser. 44 | * @return {chrome|browser} 45 | */ 46 | getApi() { 47 | return this.namespace; 48 | } 49 | 50 | /** 51 | * Checks if the current browser is Firefox. 52 | * @return {boolean} true if the current browser is Firefox, otherwise false. 53 | */ 54 | isFirefox() { 55 | return Env.browserName === Browsers.Firefox; 56 | } 57 | 58 | /** 59 | * Checks if the current browser is Chrome. 60 | * @return {boolean} true if the current browser is Chrome, otherwise false. 61 | */ 62 | isChrome() { 63 | return Env.browserName === Browsers.Chrome; 64 | } 65 | 66 | /** 67 | * Checks if the current browser is Edge. 68 | * @return {boolean} true if the current browser is Edge, otherwise false. 69 | */ 70 | isEdge() { 71 | return Env.browserName === Browsers.Edge; 72 | } 73 | 74 | /** 75 | * Checks if the current browser is Safari. 76 | * @return {boolean} true if the current browser is Safari, otherwise false. 77 | */ 78 | isSafari() { 79 | return Env.browserName === Browsers.Safari; 80 | } 81 | 82 | /** 83 | * Checks if the current browser's API supports promises. 84 | * @return {boolean} true if the current browser's API supports promises, 85 | * otherwise false. 86 | */ 87 | supportsPromises() { 88 | return this.supportPromises; 89 | } 90 | 91 | /** 92 | * Checks if the current browser supports the Sidepanel API. 93 | * @return {boolean} true if the current browser supports the Sidepanel API, 94 | * otherwise false. 95 | */ 96 | supportsSidePanel() { 97 | return this.supportSidePanel; 98 | } 99 | 100 | /** 101 | * Gets the current browser name. 102 | * @return {string} The browser name. 103 | */ 104 | getBrowserName() { 105 | return Env.browserName; 106 | } 107 | 108 | /** 109 | * Overrides the detected browser name. 110 | * @param {string} browserName The new browser name to set. 111 | */ 112 | overrideBrowserName(browserName) { 113 | Env.browserName = browserName; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /interface/lib/permissionHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * interface/devtools/permissionHandler.js needs to be kept in sync to the functions in this file 3 | */ 4 | export class PermissionHandler { 5 | /** 6 | * Constructs a PermissionHandler. 7 | * @param {BrowserDetector} browserDetector 8 | */ 9 | constructor(browserDetector) { 10 | this.browserDetector = browserDetector; 11 | // Urls that start with these values can't be requested for permission. 12 | this.impossibleUrls = [ 13 | 'about:', 14 | 'moz-extension:', 15 | 'chrome:', 16 | 'chrome-extension:', 17 | 'edge:', 18 | 'safari-web-extension:', 19 | ]; 20 | } 21 | 22 | /** 23 | * Check if it is possible for a website to have permissions. for example, on 24 | * firefox, it is impossible to check for permission on internal pages 25 | * (about:[...]). 26 | * @param {*} url Url to check. 27 | * @return {boolean} True if it's possible to request permission, otherwise 28 | * false. 29 | */ 30 | canHavePermissions(url) { 31 | if (url === '') { 32 | return false; 33 | } 34 | for (const impossibleUrl of this.impossibleUrls) { 35 | if (url.indexOf(impossibleUrl) === 0) { 36 | return false; 37 | } 38 | } 39 | return true; 40 | } 41 | 42 | /** 43 | * Checks if the extension has permissions to access the cookies for a 44 | * specific url. 45 | * @param {string} url Url to check. 46 | * @return {Promise} 47 | */ 48 | async checkPermissions(url) { 49 | const testPermission = { 50 | origins: [url], 51 | }; 52 | try { 53 | const { protocol, hostname } = new URL(url); 54 | const rootDomain = this.getRootDomainName(hostname); 55 | testPermission.origins = [ 56 | `${protocol}//${hostname}/*`, 57 | `${protocol}//*.${rootDomain}/*`, 58 | ]; 59 | } catch (err) { 60 | console.error(err); 61 | } 62 | 63 | // If we don't have access to the permission API, assume we have 64 | // access. Safari devtools can't access the API. 65 | if (typeof this.browserDetector.getApi().permissions === 'undefined') { 66 | return true; 67 | } 68 | 69 | return await this.browserDetector 70 | .getApi() 71 | .permissions.contains(testPermission); 72 | } 73 | 74 | /** 75 | * Requests permissions to access the cookies for a specific url. 76 | * @param {string} url Url to request permissions. 77 | * @return {Promise} 78 | */ 79 | async requestPermission(url) { 80 | const permission = { 81 | origins: [url], 82 | }; 83 | try { 84 | const { protocol, hostname } = new URL(url); 85 | const rootDomain = this.getRootDomainName(hostname); 86 | permission.origins = [ 87 | `${protocol}//${hostname}/*`, 88 | `${protocol}//*.${rootDomain}/*`, 89 | ]; 90 | } catch (err) { 91 | console.error(err); 92 | } 93 | return this.browserDetector.getApi().permissions.request(permission); 94 | } 95 | 96 | /** 97 | * Gets the root domain of an URL 98 | * @param {string} domain 99 | * @return {string} 100 | */ 101 | getRootDomainName(domain) { 102 | const parts = domain.split('.').reverse(); 103 | const cnt = parts.length; 104 | if (cnt >= 3) { 105 | // see if the second level domain is a common SLD. 106 | if (parts[1].match(/^(com|edu|gov|net|mil|org|nom|co|name|info|biz)$/i)) { 107 | return parts[2] + '.' + parts[1] + '.' + parts[0]; 108 | } 109 | } 110 | return parts[1] + '.' + parts[0]; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![grunt ESLint](https://github.com/Moustachauve/cookie-editor/actions/workflows/npm-grunt.yml/badge.svg)](https://github.com/Moustachauve/cookie-editor/actions/workflows/npm-grunt.yml) 2 | # Cookie-Editor 3 | [Cookie-Editor](https://cookie-editor.com/) is a browser extension/add-on that lets you efficiently create, edit and delete cookies for the current tab. Perfect for developing, quickly testing or even manually managing your cookies for your privacy. 4 | 5 | ## Description 6 | Cookie-Editor is designed to have a simple to use interface that let you do most standard cookie operations quickly. It is ideal for developing and testing web pages. 7 | 8 | You can easily create, edit and delete a cookie for the current page that you are visiting. 9 | There is also a handy button to mass delete all the cookies for the current page. 10 | 11 | Cookie-Editor is available for: 12 | - Google Chrome 13 | - Firefox 14 | - Safari 15 | - Edge 16 | - Opera. 17 | 18 | It should be possible to install it on any webkit browser, but keep in mind that only the previous five browsers are officially supported. 19 | 20 | Cookie-Editor is available on mobile devices with an interface optimised for touchscreens: 21 | - Firefox for Android 22 | - Safari for iOS 23 | - Edge for Android and iOS 24 | 25 | ## Installation 26 | ### Install on Google Chrome 27 | Find this extension on the [Chrome Web Store](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm?utm_campaign=github). 28 | [![Chrome Web Store](readme/get-chrome.png)](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm?utm_campaign=github) 29 | 30 | ### Install on Firefox 31 | Find this extension on the [Firefox Add-ons site](https://addons.mozilla.org/addon/cookie-editor?utm_campaign=external-github-readme). 32 | [![Firefox Add-ons](readme/get-firefox.webp)](https://addons.mozilla.org/addon/cookie-editor?utm_campaign=external-github-readme) 33 | 34 | ### Install on Safari 35 | Cookie-Editor is available for both Mac and iOS. It has been tested on Mac, iPhone and iPad. 36 | Find this extension on the [App Store](https://apps.apple.com/app/apple-store/id6446215341?pt=126143671&ct=github&mt=8). 37 | [![Apple App Store](readme/get-safari-mac.svg)](https://apps.apple.com/app/apple-store/id6446215341?pt=126143671&ct=github&mt=8) 38 | 39 | ### Install on Microsoft Edge 40 | Find this extension on the [Microsoft Store](https://microsoftedge.microsoft.com/addons/detail/cookieeditor/neaplmfkghagebokkhpjpoebhdledlfi). 41 | 42 | ### Install on Opera 43 | Find this extension on the [Opera Extensions site](https://addons.opera.com/en/extensions/details/cookie-editor-2/). 44 | [![Opera add-ons](readme/get-opera.png )](https://addons.opera.com/en/extensions/details/cookie-editor-2/) 45 | 46 | ## Feature Suggestions or Bug Reports 47 | To submit a feature suggestion or file a bug report, please [create a new issue here](https://github.com/Moustachauve/cookie-editor/issues). 48 | 49 | ## How to build 50 | 51 | 1. Run npm install to make sure you have all the required packages installed. 52 | 2. Run the command `grunt` 53 | 3. All the files are in the `dist` directory created 54 | 55 | ### Note for Safari 56 | 57 | Safari needs to be built in Xcode. I have only tested building Cookie-Editor on Xcode 15. 58 | 59 | ## Thanks 60 | 61 | Thanks to DigitalOcean for supporting open-source software. 62 | 63 |

64 | 65 | Powered by DigitalOcean 66 | 67 |

68 | 69 | ## Disclaimer 70 | 71 | This project is not an official Google project. It is not supported by 72 | Google and Google specifically disclaims all warranties as to its quality, 73 | merchantability, or fitness for a particular purpose. 74 | -------------------------------------------------------------------------------- /interface/popup/cookieHandlerPopup.js: -------------------------------------------------------------------------------- 1 | import { GenericCookieHandler } from '../lib/genericCookieHandler.js'; 2 | 3 | /** 4 | * implements Cookie API handling for the popup and other similar interfaces. 5 | */ 6 | export class CookieHandlerPopup extends GenericCookieHandler { 7 | /** 8 | * Constructs and initializes the cookie handler. 9 | * @param {BrowserDetector} browserDetector 10 | */ 11 | constructor(browserDetector) { 12 | super(browserDetector); 13 | console.log('Constructing PopupCookieHandler'); 14 | this.isReady = false; 15 | this.currentTabId = null; 16 | 17 | if (this.browserDetector.supportsPromises()) { 18 | this.browserDetector 19 | .getApi() 20 | .tabs.query({ active: true, currentWindow: true }) 21 | .then(this.init); 22 | } else { 23 | this.browserDetector 24 | .getApi() 25 | .tabs.query({ active: true, currentWindow: true }, this.init); 26 | } 27 | } 28 | 29 | /** 30 | * Initialise the cookie handler after getting our first contact with the 31 | * current tab. 32 | * @param {*} tabInfo Info about the current tab. 33 | */ 34 | init = tabInfo => { 35 | this.currentTabId = tabInfo[0].id; 36 | this.currentTab = tabInfo[0]; 37 | const api = this.browserDetector.getApi(); 38 | api.tabs.onUpdated.addListener(this.onTabsChanged); 39 | api.tabs.onActivated.addListener(this.onTabActivated); 40 | if (!this.browserDetector.isSafari()) { 41 | api.cookies.onChanged.addListener(this.onCookiesChanged); 42 | } 43 | 44 | this.emit('ready'); 45 | this.isReady = true; 46 | }; 47 | 48 | /** 49 | * Handles events that is triggered when a cookie changes. 50 | * @param {object} changeInfo An object containing details of the change that 51 | * occurred. 52 | */ 53 | onCookiesChanged = changeInfo => { 54 | const domain = changeInfo.cookie.domain.substring(1); 55 | if ( 56 | this.currentTab.url.indexOf(domain) !== -1 && 57 | changeInfo.cookie.storeId === (this.currentTab.cookieStoreId || '0') 58 | ) { 59 | this.emit('cookiesChanged', changeInfo); 60 | } 61 | }; 62 | 63 | /** 64 | * Handles the event that is fired when a tab is updated. 65 | * @param {object} tabId Id of the tab that changed. 66 | * @param {object} changeInfo Properties of the tab that changed. 67 | * @param {object} _tab 68 | */ 69 | onTabsChanged = (tabId, changeInfo, _tab) => { 70 | if ( 71 | tabId === this.currentTabId && 72 | (changeInfo.url || changeInfo.status === 'complete') 73 | ) { 74 | console.log('tabChanged!'); 75 | if (this.browserDetector.supportsPromises()) { 76 | this.browserDetector 77 | .getApi() 78 | .tabs.query({ active: true, currentWindow: true }) 79 | .then(this.updateCurrentTab); 80 | } else { 81 | this.browserDetector 82 | .getApi() 83 | .tabs.query( 84 | { active: true, currentWindow: true }, 85 | this.updateCurrentTab 86 | ); 87 | } 88 | } 89 | }; 90 | 91 | /** 92 | * Event handler for when a tab is being activated. 93 | * @param {object} activeInfo Info about the event. 94 | */ 95 | onTabActivated = activeInfo => { 96 | if (this.browserDetector.supportsPromises()) { 97 | this.browserDetector 98 | .getApi() 99 | .tabs.query({ active: true, currentWindow: true }) 100 | .then(this.updateCurrentTab); 101 | } else { 102 | this.browserDetector 103 | .getApi() 104 | .tabs.query( 105 | { active: true, currentWindow: true }, 106 | this.updateCurrentTab 107 | ); 108 | } 109 | }; 110 | 111 | /** 112 | * Emits a signal that the current tab changed if needed. 113 | * @param {object} tabInfo Info about the new current tab. 114 | */ 115 | updateCurrentTab = tabInfo => { 116 | const newTab = 117 | tabInfo[0].id !== this.currentTabId || 118 | tabInfo[0].url !== this.currentTab.url; 119 | this.currentTabId = tabInfo[0].id; 120 | this.currentTab = tabInfo[0]; 121 | 122 | if (newTab && this.isReady) { 123 | this.emit('cookiesChanged'); 124 | } 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /site/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 404 Page not found - Cookie-Editor 18 | 19 | 20 | 21 |
22 | 29 |
30 | 31 |
32 |
33 | 34 |

404 Page Not Found

35 |

Oops, it seems like this page doesn't exists.

36 | Go back to the home page. 37 |
38 | 39 | 40 | 41 |
42 |
43 | 44 |
45 |

46 | Moustachauve 47 | / 48 | Cookie-Editor 49 |

50 |

51 | You can find the source code of this project on Github. Contribute as you desire to the project. All the 52 | help is appreciated. 53 |

54 |
55 |
56 |
57 |
58 | 59 |
60 |
61 |

Suggestions or Bug Report

62 |

63 | Please create an issue on the Github issue tracker for any suggestion or bug report. 64 | It will be resolved as fast as possible. 65 |

66 | 67 | Create an Issue 68 | 69 |
70 |
71 |
72 | 73 | 88 | 89 |
90 | 91 | 92 | -------------------------------------------------------------------------------- /site/privacy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Privacy Policy - Cookie-Editor 17 | 18 | 19 | 20 |
21 | 28 |
29 | 30 |
31 |
32 |

Privacy Policy

33 |

It's simple: the Cookie-Editor extension does not collect any data at all.

34 |
35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 |

43 | Moustachauve 44 | / 45 | Cookie-Editor 46 |

47 |

48 | You can find the source code of this project on Github. Contribute as you desire to the project. All the 49 | help is appreciated. 50 |

51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |

Suggestions or Bug Report

59 |

60 | Please create an issue on the Github issue tracker for any suggestion or bug report. 61 | It will be resolved as fast as possible. 62 |

63 | 64 | Create an Issue 65 | 66 |
67 |
68 |
69 | 70 | 85 | 86 |
87 | 88 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /icons/cookie-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /icons/cookie-light-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /icons/cookie-filled-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/cookie.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /icons/cookie-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /icons/cookie-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /site/img/cookie-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /interface/devtools/cookieHandlerDevtools.js: -------------------------------------------------------------------------------- 1 | import { GenericCookieHandler } from '../lib/genericCookieHandler.js'; 2 | 3 | /** 4 | * implements Cookie API handling for the devtools. 5 | * Devtools needs a separate behavior because they don't have access to the same 6 | * APIs as the popup, for example. 7 | */ 8 | export class CookieHandlerDevtools extends GenericCookieHandler { 9 | /** 10 | * Constructs and initializes the cookie handler. 11 | * @param {BrowserDetector} browserDetector 12 | */ 13 | constructor(browserDetector) { 14 | super(browserDetector); 15 | this.isReady = false; 16 | console.log('Constructing DevToolsCookieHandler'); 17 | this.backgroundPageConnection = this.browserDetector 18 | .getApi() 19 | .runtime.connect({ name: 'panel' }); 20 | this.updateCurrentTab(this.init); 21 | } 22 | 23 | /** 24 | * Initialise the cookie handler after making first contact with the main 25 | * background script. 26 | */ 27 | init = () => { 28 | console.log('Devtool init'); 29 | this.backgroundPageConnection.onMessage.addListener(this.onMessage); 30 | this.backgroundPageConnection.postMessage({ 31 | type: 'init_cookieHandler', 32 | tabId: this.browserDetector.getApi().devtools.inspectedWindow.tabId, 33 | }); 34 | 35 | console.log('Devtool ready'); 36 | this.emit('ready'); 37 | this.isReady = true; 38 | }; 39 | 40 | /** 41 | * Gets all the cookies for the current tab. 42 | * @param {function} callback 43 | */ 44 | getAllCookies(callback) { 45 | this.sendMessage( 46 | 'getAllCookies', 47 | { 48 | url: this.currentTab.url, 49 | storeId: this.currentTab.cookieStoreId, 50 | }, 51 | callback 52 | ); 53 | } 54 | 55 | /** 56 | * Saves a cookie. This can either create a new cookie or modify an existing 57 | * one. 58 | * @param {Cookie} cookie Cookie's data. 59 | * @param {string} url The url to attach the cookie to. 60 | * @param {function} callback 61 | */ 62 | saveCookie(cookie, url, callback) { 63 | this.sendMessage( 64 | 'saveCookie', 65 | { cookie: this.prepareCookie(cookie, url) }, 66 | callback 67 | ); 68 | } 69 | 70 | /** 71 | * Removes a cookie from the browser. 72 | * @param {string} name The name of the cookie to remove. 73 | * @param {string} url The url that the cookie is attached to. 74 | * @param {function} callback 75 | */ 76 | removeCookie(name, url, callback) { 77 | this.sendMessage( 78 | 'removeCookie', 79 | { 80 | name: name, 81 | url: url, 82 | storeId: this.currentTab.cookieStoreId, 83 | }, 84 | callback 85 | ); 86 | } 87 | 88 | /** 89 | * Handles the reception of messages from the background script. 90 | * @param {object} request 91 | */ 92 | onMessage = request => { 93 | console.log( 94 | '[cookieHandler] background message received: ' + 95 | (request.type || 'unknown') 96 | ); 97 | switch (request.type) { 98 | case 'cookiesChanged': 99 | this.onCookiesChanged(request.data); 100 | return; 101 | 102 | case 'tabsChanged': 103 | this.onTabsChanged(request.data); 104 | return; 105 | } 106 | }; 107 | 108 | /** 109 | * Handles events that is triggered when a cookie changes. 110 | * @param {object} changeInfo An object containing details of the change that 111 | * occurred. 112 | */ 113 | onCookiesChanged = changeInfo => { 114 | const domain = changeInfo.cookie.domain.substring(1); 115 | if (this.currentTab.url.indexOf(domain) !== -1) { 116 | this.emit('cookiesChanged', changeInfo); 117 | } 118 | }; 119 | 120 | /** 121 | * Handles the event that is fired when a tab is updated. 122 | * @param {object} changeInfo Properties of the tab that changed. 123 | */ 124 | onTabsChanged = changeInfo => { 125 | console.log('devtools: tab changed', changeInfo); 126 | if (changeInfo.url || changeInfo.status === 'complete') { 127 | console.log('tabChanged!'); 128 | this.updateCurrentTab(); 129 | } 130 | }; 131 | 132 | /** 133 | * Retrieves the informations of the current tab from the background script. 134 | * @param {*} callback 135 | */ 136 | updateCurrentTab = callback => { 137 | const self = this; 138 | this.sendMessage( 139 | 'getCurrentTab', 140 | null, 141 | function (tabInfo) { 142 | const newTab = 143 | tabInfo[0].id !== self.currentTabId || 144 | tabInfo[0].url !== self.currentTab.url; 145 | self.currentTabId = tabInfo[0].id; 146 | self.currentTab = tabInfo[0]; 147 | if (newTab && self.isReady) { 148 | self.emit('cookiesChanged'); 149 | } 150 | if (callback) { 151 | callback(); 152 | } 153 | }, 154 | function (e) { 155 | console.log('failed to update current tab', e); 156 | } 157 | ); 158 | }; 159 | 160 | /** 161 | * Sends a message to the background script. 162 | * @param {string} type The type of the message. 163 | * @param {object} params The payload of the message 164 | * @param {function} callback 165 | * @param {function} errorCallback 166 | */ 167 | sendMessage(type, params, callback, errorCallback) { 168 | if (this.browserDetector.supportsPromises()) { 169 | this.browserDetector 170 | .getApi() 171 | .runtime.sendMessage({ type: type, params: params }) 172 | .then(callback, errorCallback); 173 | } else { 174 | this.browserDetector 175 | .getApi() 176 | .runtime.sendMessage({ type: type, params: params }, callback); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | christopheextensions@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /site/img/digitalocean-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /interface/lib/ads/adHandler.js: -------------------------------------------------------------------------------- 1 | import { Browsers } from '../browsers.js'; 2 | import { ActiveAds } from './activeAds.js'; 3 | 4 | const secondsInOneDay = Object.freeze(1 * 24 * 60 * 60 * 1000); 5 | 6 | /** 7 | * class used to handle all the ad logic. 8 | */ 9 | export class AdHandler { 10 | /** 11 | * Constructs an AdHandler. 12 | * @param {BrowserDetector} browserDetector 13 | * @param {GenericStorageHandler} storageHandler 14 | * @param {OptionsHandler} optionHandler 15 | */ 16 | constructor(browserDetector, storageHandler, optionHandler) { 17 | this.browserDetector = browserDetector; 18 | this.storageHandler = storageHandler; 19 | this.optionHandler = optionHandler; 20 | } 21 | 22 | /** 23 | * Checks if an ad is valid to display to the user. To be valid, an ad needs 24 | * to be available for the user's browser and respect the choice of the user 25 | * if they have marked it as not interested. 26 | * @param {object} selectedAd The ad to validate. 27 | */ 28 | async isAdValid(selectedAd) { 29 | if ( 30 | selectedAd.supportedBrowsers != Browsers.Any && 31 | !selectedAd.supportedBrowsers.includes( 32 | this.browserDetector.getBrowserName() 33 | ) 34 | ) { 35 | return false; 36 | } 37 | 38 | const dismissedAd = await this.storageHandler.getLocal( 39 | this.getAdDismissKey(selectedAd.id) 40 | ); 41 | // No data means it was never dismissed 42 | if (dismissedAd === null) { 43 | return true; 44 | } 45 | 46 | // Only show a ad if it has not been dismissed in less than |ad.refreshDays| 47 | // days 48 | if ( 49 | secondsInOneDay * selectedAd.refreshDays + new Date().getTime() > 50 | dismissedAd.date 51 | ) { 52 | console.log('Not showing ad ' + selectedAd.id + ', it was dismissed.'); 53 | return false; 54 | } 55 | return true; 56 | } 57 | 58 | /** 59 | * Checks if an ad timeframe is currently active. 60 | * @param {Ad} selectedAd 61 | * @return {boolean} True if the time is valid, otherwise false. 62 | */ 63 | isAdTimeframeValid(selectedAd) { 64 | if (selectedAd.startDate === null && selectedAd.endDate === null) { 65 | return true; 66 | } 67 | const now = new Date().getTime(); 68 | 69 | if (selectedAd.startDate !== null && selectedAd.startDate.getTime() > now) { 70 | console.log('ad is not started yet'); 71 | return false; 72 | } 73 | if (selectedAd.endDate !== null && selectedAd.endDate.getTime() < now) { 74 | console.log('ad is already finished'); 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | 81 | /** 82 | * Makes sure to not spam the user with ads if they recently dismissed one. 83 | * @param {function} callback 84 | */ 85 | async canShowAnyAd() { 86 | if (ActiveAds.length === 0) { 87 | return false; 88 | } 89 | if (!this.optionHandler.getAdsEnabled()) { 90 | return false; 91 | } 92 | 93 | const lastDismissedAd = await this.storageHandler.getLocal( 94 | this.getLastDismissKey() 95 | ); 96 | // No data means it was never dismissed 97 | if (lastDismissedAd === null) { 98 | return true; 99 | } 100 | // Don't show more ad if one was dismissed in less than 24hrs 101 | if (new Date().getTime() - secondsInOneDay < lastDismissedAd.date) { 102 | console.log('Not showing ads, one was dismissed recently.'); 103 | return false; 104 | } 105 | return true; 106 | } 107 | 108 | /** 109 | * Gets a random valid ad that can be displayed to the user. 110 | * @param {Ad[]|null} [adList=null] List of ad to chose from. Used for 111 | * recursion. 112 | * @return {Ad|false} Returns a valid ad if one is found, otherwise false. 113 | */ 114 | async getRandomValidAd(adList = null) { 115 | if (adList === null) { 116 | adList = Array.from(ActiveAds); 117 | } 118 | if (!adList || !adList.length) { 119 | console.log('No ads left'); 120 | return false; 121 | } 122 | const randIndex = Math.floor(Math.random() * adList.length); 123 | const selectedAd = adList[randIndex]; 124 | adList.splice(randIndex, 1); 125 | const isAdValid = await this.isAdValid(selectedAd); 126 | if (!isAdValid) { 127 | console.log(selectedAd.id, 'ad is not valid to display.'); 128 | return this.getRandomValidAd(adList); 129 | } 130 | if (!this.isAdTimeframeValid(selectedAd)) { 131 | console.log(selectedAd.id, 'ad is not in the current timeframe.'); 132 | return this.getRandomValidAd(adList); 133 | } 134 | return selectedAd; 135 | } 136 | 137 | /** 138 | * Marks an ad as dismissed so it doesn't show up for a while. 139 | * @param {Ad} adObject 140 | */ 141 | async markAdAsDismissed(adObject) { 142 | await this.storageHandler.setLocal( 143 | this.getAdDismissKey(adObject.id), 144 | this.createDismissObjV1() 145 | ); 146 | await this.storageHandler.setLocal( 147 | this.getLastDismissKey(), 148 | this.createDismissObjV1() 149 | ); 150 | } 151 | 152 | /** 153 | * Gets the key to get the last dismissed ad. 154 | * @return {string} The key. 155 | */ 156 | getLastDismissKey() { 157 | return 'adDismissLast'; 158 | } 159 | 160 | /** 161 | * Gets the key to get the time a specific ad was dismissed. 162 | * @param {string} id Id of the ad to check. 163 | * @return {string} The key. 164 | */ 165 | getAdDismissKey(id) { 166 | return 'adDismiss.' + id; 167 | } 168 | 169 | /** 170 | * Creates the data to log the time a specific ad was dismissed. 171 | * @return {object} Data about the dismissal. 172 | */ 173 | createDismissObjV1() { 174 | return { 175 | version: 1, 176 | date: Date.now(), 177 | }; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /interface/lib/ads/activeAds.js: -------------------------------------------------------------------------------- 1 | import { Browsers } from '../browsers.js'; 2 | import { Ad } from './ad.js'; 3 | 4 | /** 5 | * A list of all the currently active ads. 6 | */ 7 | export const ActiveAds = Object.freeze([ 8 | new Ad({ 9 | id: 'cookie-editor', 10 | text: 'Enjoying Cookie-Editor? Buy me a coffee!', 11 | tooltip: 12 | 'Cookie-Editor is always free. Help its development by sponsoring me.', 13 | url: 'https://github.com/sponsors/Moustachauve', 14 | // Apple doesn't allow Github sponsors (external payment) 15 | supportedBrowsers: [ 16 | Browsers.Chrome, 17 | Browsers.Edge, 18 | Browsers.Firefox, 19 | Browsers.Opera, 20 | ], 21 | refreshDays: 80, 22 | startDate: null, 23 | endDate: null, 24 | }), 25 | 26 | new Ad({ 27 | id: 'tab-for-cause', 28 | text: 'Get Tab For A Cause: Raise money for charity', 29 | tooltip: 30 | "Raise money for charity every time you open a new browser tab. It's free and incredibly easy. Transform your tabs into a force for good in 30 seconds.", 31 | url: 'https://tab.gladly.io/cookieeditor/', 32 | supportedBrowsers: [Browsers.Chrome, Browsers.Edge, Browsers.Safari], 33 | refreshDays: 90, 34 | startDate: new Date('2023/09/01'), 35 | endDate: new Date('2024/02/29'), 36 | }), 37 | 38 | new Ad({ 39 | id: 'tab-for-cause2', 40 | text: 'Have 47+ tabs open? Try Tab for a Cause to raise money for non-profits', 41 | tooltip: 42 | "Raise money for charity every time you open a new browser tab. It's free and incredibly easy. Transform your tabs into a force for good in 30 seconds.", 43 | url: 'https://tab.gladly.io/cookieeditor/', 44 | supportedBrowsers: [Browsers.Chrome, Browsers.Edge, Browsers.Safari], 45 | refreshDays: 90, 46 | startDate: new Date('2023/04/01'), 47 | endDate: new Date('2025/09/01'), 48 | }), 49 | 50 | new Ad({ 51 | id: 'skillshare', 52 | text: 'Skillshare | Explore your creativity with thousands of hands‑on classes.', 53 | tooltip: 'Join Skillshare Today and Get 30% Off Annual Membership.', 54 | url: 'https://skillshare.eqcm.net/Mmo4oM', 55 | supportedBrowsers: Browsers.Any, 56 | refreshDays: 110, 57 | startDate: new Date('2023/10/21'), 58 | endDate: new Date('2025/05/01'), 59 | }), 60 | 61 | new Ad({ 62 | id: 'nordvpn', 63 | text: 'With NordVPN, you can browse like no one’s watching — because no one is.', 64 | tooltip: 65 | 'Get NordVPN now to protect yourself online every day, wherever you are. Securely access apps, websites, and entertainment.', 66 | url: 'https://go.nordvpn.net/aff_c?offer_id=15&aff_id=93111&url_id=902', 67 | supportedBrowsers: Browsers.Any, 68 | refreshDays: 120, 69 | startDate: new Date('2023/11/15'), 70 | endDate: new Date('2024/03/15'), 71 | }), 72 | 73 | new Ad({ 74 | id: 'nordvpn-deal', 75 | text: "Don't miss a chance and grab a limited NordVPN deal!", 76 | tooltip: 77 | 'Get NordVPN now to protect yourself online every day, wherever you are. Securely access apps, websites, and entertainment.', 78 | url: 'https://go.nordvpn.net/aff_c?offer_id=15&aff_id=93111&url_id=902', 79 | supportedBrowsers: Browsers.Any, 80 | refreshDays: 120, 81 | startDate: new Date('2024/03/16'), 82 | endDate: new Date('2024/12/31'), 83 | }), 84 | 85 | new Ad({ 86 | id: 'aura', 87 | text: 'Aura | #1 Rated Identity Theft Protection - Try Aura 14-days free.', 88 | tooltip: 89 | 'Aura protects your identity, finances and sensitive data. All plans include a $1M insurance policy that covers eligible losses.', 90 | url: 'https://aurainc.sjv.io/c/4869326/1835216/12398', 91 | supportedBrowsers: Browsers.Any, 92 | refreshDays: 130, 93 | startDate: new Date('2023/10/07'), 94 | endDate: new Date('2025/02/01'), 95 | }), 96 | 97 | new Ad({ 98 | id: 'incogni', 99 | text: 'Delete your personal data today with Incogni', 100 | tooltip: 101 | 'Thousands of companies are collecting, aggregating, and trading your personal data without you knowing anything about it. Incogni makes them remove it so your data stays secure and private.', 102 | url: 'https://get.incogni.io/aff_c?offer_id=1150&aff_id=25909', 103 | supportedBrowsers: Browsers.Any, 104 | refreshDays: 140, 105 | startDate: new Date('2023/10/07'), 106 | endDate: new Date('2024/12/01'), 107 | }), 108 | 109 | new Ad({ 110 | id: 'incogni-code', 111 | text: 'Incogni | Want to stop robocalls and spam emails today?', 112 | tooltip: 113 | 'Thousands of companies are collecting, aggregating, and trading your personal data without you knowing anything about it. Incogni makes them remove it so your data stays secure and private.', 114 | url: 'https://get.incogni.io/aff_c?offer_id=1150&aff_id=25909', 115 | supportedBrowsers: Browsers.Any, 116 | refreshDays: 140, 117 | startDate: new Date('2024/03/07'), 118 | endDate: new Date('2024/12/01'), 119 | }), 120 | 121 | new Ad({ 122 | id: 'namecheap', 123 | text: 'Namecheap | Get a .COM for just $5.98!', 124 | tooltip: 'All domains for great prices. Prices are succeptible to change.', 125 | url: 'https://namecheap.pxf.io/zNkAPe', 126 | supportedBrowsers: Browsers.Any, 127 | refreshDays: 130, 128 | startDate: new Date('2024/03/14'), 129 | endDate: new Date('2025/03/14'), 130 | }), 131 | 132 | new Ad({ 133 | id: 'curiosity-box', 134 | text: 'Try the Curiosity Box by VSauce and get a FREE Lightyear Bottle!', 135 | tooltip: "The world's best science toys by science legend, VSauce.", 136 | url: 'https://the-curiosity-box.pxf.io/DKrYOo', 137 | supportedBrowsers: Browsers.Any, 138 | refreshDays: 100, 139 | startDate: new Date('2024/03/14'), 140 | endDate: new Date('2025/03/14'), 141 | }), 142 | ]); 143 | -------------------------------------------------------------------------------- /site/img/chrome-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /interface/lib/genericCookieHandler.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './eventEmitter.js'; 2 | 3 | /** 4 | * Class used to implement basic common Cookie API handling. 5 | */ 6 | export class GenericCookieHandler extends EventEmitter { 7 | /** 8 | * Constructs a GenericCookieHandler. 9 | * @param {BrowserDetector} browserDetector 10 | */ 11 | constructor(browserDetector) { 12 | super(); 13 | this.cookies = []; 14 | this.currentTab = null; 15 | this.browserDetector = browserDetector; 16 | } 17 | 18 | /** 19 | * Gets all cookie for the current tab. 20 | * @param {function} callback 21 | */ 22 | getAllCookies(callback) { 23 | if (this.browserDetector.supportsPromises()) { 24 | this.browserDetector 25 | .getApi() 26 | .cookies.getAll({ 27 | url: this.currentTab.url, 28 | storeId: this.currentTab.cookieStoreId, 29 | }) 30 | .then(callback, function (e) { 31 | console.error('Failed to retrieve cookies', e); 32 | }); 33 | } else { 34 | this.browserDetector.getApi().cookies.getAll( 35 | { 36 | url: this.currentTab.url, 37 | storeId: this.currentTab.cookieStoreId, 38 | }, 39 | callback 40 | ); 41 | } 42 | } 43 | 44 | /** 45 | * Prepares a cookie to be saved. Cleans it up for certain browsers. 46 | * @param {object} cookie 47 | * @param {string} url 48 | * @return {object} 49 | */ 50 | prepareCookie(cookie, url) { 51 | const newCookie = { 52 | domain: cookie.domain || '', 53 | name: cookie.name || '', 54 | value: cookie.value || '', 55 | path: cookie.path || null, 56 | secure: cookie.secure || null, 57 | httpOnly: cookie.httpOnly || null, 58 | expirationDate: cookie.expirationDate || null, 59 | storeId: cookie.storeId || this.currentTab.cookieStoreId || null, 60 | url: url, 61 | }; 62 | 63 | // Bad hack on safari because cookies needs to have the very exact same domain 64 | // to be able to edit it. 65 | if (this.browserDetector.isSafari() && newCookie.domain) { 66 | newCookie.url = 'http://' + newCookie.domain; 67 | } 68 | if (this.browserDetector.isSafari() && !newCookie.path) { 69 | newCookie.path = '/'; 70 | } 71 | 72 | if ( 73 | cookie.hostOnly || 74 | (this.browserDetector.isSafari() && !newCookie.domain) 75 | ) { 76 | newCookie.domain = null; 77 | } 78 | 79 | if (!this.browserDetector.isSafari()) { 80 | newCookie.sameSite = cookie.sameSite || undefined; 81 | 82 | if (newCookie.sameSite == 'no_restriction') { 83 | newCookie.secure = true; 84 | } 85 | } 86 | 87 | return newCookie; 88 | } 89 | 90 | /** 91 | * Saves a cookie. This can either create a new cookie or modify an existing 92 | * one. 93 | * @param {Cookie} cookie Cookie's data. 94 | * @param {string} url The url to attach the cookie to. 95 | * @param {function} callback 96 | */ 97 | saveCookie(cookie, url, callback) { 98 | cookie = this.prepareCookie(cookie, url); 99 | if (this.browserDetector.supportsPromises()) { 100 | this.browserDetector 101 | .getApi() 102 | .cookies.set(cookie) 103 | .then( 104 | (cookie, a, b, c) => { 105 | if (callback) { 106 | callback(null, cookie); 107 | } 108 | }, 109 | error => { 110 | console.error('Failed to create cookie', error); 111 | if (callback) { 112 | callback(error.message, null); 113 | } 114 | } 115 | ); 116 | } else { 117 | this.browserDetector.getApi().cookies.set(cookie, cookieResponse => { 118 | const error = this.browserDetector.getApi().runtime.lastError; 119 | if (!cookieResponse || error) { 120 | console.error('Failed to create cookie', error); 121 | if (callback) { 122 | const errorMessage = 123 | (error ? error.message : '') || 'Unknown error'; 124 | return callback(errorMessage, cookieResponse); 125 | } 126 | return; 127 | } 128 | 129 | if (callback) { 130 | return callback(null, cookieResponse); 131 | } 132 | }); 133 | } 134 | } 135 | 136 | /** 137 | * Removes a cookie from the browser. 138 | * @param {string} name The name of the cookie to remove. 139 | * @param {string} url The url that the cookie is attached to. 140 | * @param {function} callback 141 | * @param {boolean} isRecursive 142 | */ 143 | removeCookie(name, url, callback, isRecursive = false) { 144 | // Bad hack on safari because cookies needs to have the very exact same domain 145 | // to be able to delete it. 146 | // TODO: Check if this hack is needed on devtools. 147 | if (this.browserDetector.isSafari() && !isRecursive) { 148 | this.getAllCookies(cookies => { 149 | for (const cookie of cookies) { 150 | if (cookie.name === name) { 151 | this.removeCookie(name, 'http://' + cookie.domain, callback, true); 152 | } 153 | } 154 | }); 155 | } else if (this.browserDetector.supportsPromises()) { 156 | this.browserDetector 157 | .getApi() 158 | .cookies.remove({ 159 | name: name, 160 | url: url, 161 | storeId: this.currentTab.cookieStoreId, 162 | }) 163 | .then(callback, function (e) { 164 | console.error('Failed to remove cookies', e); 165 | if (callback) { 166 | callback(); 167 | } 168 | }); 169 | } else { 170 | this.browserDetector.getApi().cookies.remove( 171 | { 172 | name: name, 173 | url: url, 174 | storeId: this.currentTab.cookieStoreId, 175 | }, 176 | cookieResponse => { 177 | const error = this.browserDetector.getApi().runtime.lastError; 178 | if (!cookieResponse || error) { 179 | console.error('Failed to remove cookie', error); 180 | if (callback) { 181 | const errorMessage = 182 | (error ? error.message : '') || 'Unknown error'; 183 | return callback(errorMessage, cookieResponse); 184 | } 185 | return; 186 | } 187 | 188 | if (callback) { 189 | return callback(null, cookieResponse); 190 | } 191 | } 192 | ); 193 | } 194 | } 195 | 196 | /** 197 | * Gets all the cookies from the browser. 198 | * @param {function} callback 199 | */ 200 | getAllCookiesInBrowser(callback) { 201 | if (this.browserDetector.supportsPromises()) { 202 | this.browserDetector 203 | .getApi() 204 | .cookies.getAll({}) 205 | .then(callback, function (e) { 206 | console.error('Failed to retrieve cookies', e); 207 | }); 208 | } else { 209 | this.browserDetector.getApi().cookies.getAll({}, callback); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /interface/options/options.js: -------------------------------------------------------------------------------- 1 | import { BrowserDetector } from '../lib/browserDetector.js'; 2 | import { Cookie } from '../lib/cookie.js'; 3 | import { GenericStorageHandler } from '../lib/genericStorageHandler.js'; 4 | import { JsonFormat } from '../lib/jsonFormat.js'; 5 | import { NetscapeFormat } from '../lib/netscapeFormat.js'; 6 | import { OptionsHandler } from '../lib/optionsHandler.js'; 7 | import { PermissionHandler } from '../lib/permissionHandler.js'; 8 | import { ThemeHandler } from '../lib/themeHandler.js'; 9 | import { CookieHandlerPopup } from '../popup/cookieHandlerPopup.js'; 10 | 11 | document.addEventListener('DOMContentLoaded', async event => { 12 | const browserDetector = new BrowserDetector(); 13 | const storageHandler = new GenericStorageHandler(browserDetector); 14 | const optionHandler = new OptionsHandler(browserDetector, storageHandler); 15 | const themeHandler = new ThemeHandler(optionHandler); 16 | const cookieHandler = new CookieHandlerPopup(browserDetector); 17 | const permissionHandler = new PermissionHandler(browserDetector); 18 | const advancedCookieInput = document.getElementById('advanced-cookie'); 19 | const showDevtoolsInput = document.getElementById('devtool-show'); 20 | const animationsEnabledInput = document.getElementById('animations-enabled'); 21 | const exportFormatInput = document.getElementById('export-format'); 22 | const extraInfoInput = document.getElementById('extra-info'); 23 | const themeInput = document.getElementById('theme'); 24 | const buttonBarTopInput = document.getElementById('button-bar-top'); 25 | const adsEnabledInput = document.getElementById('ads-enabled'); 26 | 27 | await optionHandler.loadOptions(); 28 | themeHandler.updateTheme(); 29 | setFormValues(); 30 | optionHandler.on('optionsChanged', setFormValues); 31 | setInputEvents(); 32 | 33 | /** 34 | * Sets the value of the form based on the saved options. 35 | */ 36 | function setFormValues() { 37 | console.log('Setting up the form'); 38 | handleAnimationsEnabled(); 39 | advancedCookieInput.checked = optionHandler.getCookieAdvanced(); 40 | showDevtoolsInput.checked = optionHandler.getDevtoolsEnabled(); 41 | animationsEnabledInput.checked = optionHandler.getAnimationsEnabled(); 42 | exportFormatInput.value = optionHandler.getExportFormat(); 43 | extraInfoInput.value = optionHandler.getExtraInfo(); 44 | themeInput.value = optionHandler.getTheme(); 45 | buttonBarTopInput.checked = optionHandler.getButtonBarTop(); 46 | adsEnabledInput.checked = optionHandler.getAdsEnabled(); 47 | 48 | if (!browserDetector.isSafari()) { 49 | document 50 | .querySelectorAll('.github-sponsor') 51 | .forEach(el => el.classList.remove('hidden')); 52 | } 53 | } 54 | 55 | /** 56 | * Sets the different input listeners to save the form changes. 57 | */ 58 | function setInputEvents() { 59 | advancedCookieInput.addEventListener('change', event => { 60 | if (!event.isTrusted) { 61 | return; 62 | } 63 | optionHandler.setCookieAdvanced(advancedCookieInput.checked); 64 | }); 65 | showDevtoolsInput.addEventListener('change', event => { 66 | if (!event.isTrusted) { 67 | return; 68 | } 69 | optionHandler.setDevtoolsEnabled(showDevtoolsInput.checked); 70 | }); 71 | animationsEnabledInput.addEventListener('change', event => { 72 | if (!event.isTrusted) { 73 | return; 74 | } 75 | optionHandler.setAnimationsEnabled(animationsEnabledInput.checked); 76 | handleAnimationsEnabled(); 77 | }); 78 | exportFormatInput.addEventListener('change', event => { 79 | if (!event.isTrusted) { 80 | return; 81 | } 82 | optionHandler.setExportFormat(exportFormatInput.value); 83 | }); 84 | extraInfoInput.addEventListener('change', event => { 85 | if (!event.isTrusted) { 86 | return; 87 | } 88 | optionHandler.setExtraInfo(extraInfoInput.value); 89 | }); 90 | themeInput.addEventListener('change', event => { 91 | if (!event.isTrusted) { 92 | return; 93 | } 94 | optionHandler.setTheme(themeInput.value); 95 | themeHandler.updateTheme(); 96 | }); 97 | buttonBarTopInput.addEventListener('change', event => { 98 | if (!event.isTrusted) { 99 | return; 100 | } 101 | optionHandler.setButtonBarTop(buttonBarTopInput.checked); 102 | }); 103 | adsEnabledInput.addEventListener('change', event => { 104 | if (!event.isTrusted) { 105 | return; 106 | } 107 | optionHandler.setAdsEnabled(adsEnabledInput.checked); 108 | }); 109 | 110 | document 111 | .getElementById('delete-all') 112 | .addEventListener('click', async event => { 113 | await deleteAllCookies(); 114 | }); 115 | 116 | document 117 | .getElementById('export-all-json') 118 | .addEventListener('click', async event => { 119 | await exportCookiesAsJson(); 120 | }); 121 | 122 | document 123 | .getElementById('export-all-netscape') 124 | .addEventListener('click', async event => { 125 | await exportCookiesAsNetscape(); 126 | }); 127 | } 128 | 129 | /** 130 | * Get permissions for All urls. 131 | */ 132 | async function getAllPermissions() { 133 | const hasPermissions = 134 | await permissionHandler.checkPermissions(''); 135 | if (!hasPermissions) { 136 | await permissionHandler.requestPermission(''); 137 | } 138 | } 139 | 140 | /** 141 | * Get all cookies for the browser 142 | */ 143 | async function getAllCookies() { 144 | await getAllPermissions(); 145 | return new Promise((resolve, reject) => { 146 | cookieHandler.getAllCookiesInBrowser(function (cookies) { 147 | const loadedCookies = []; 148 | for (const cookie of cookies) { 149 | const id = Cookie.hashCode(cookie); 150 | loadedCookies[id] = new Cookie(id, cookie, optionHandler); 151 | } 152 | resolve(loadedCookies); 153 | }); 154 | }); 155 | } 156 | 157 | /** 158 | * Delete all cookies. 159 | */ 160 | async function deleteAllCookies() { 161 | const deleteAll = confirm( 162 | 'Are you sure you want to delete ALL your cookies?' 163 | ); 164 | if (!deleteAll) { 165 | return; 166 | } 167 | const cookies = await getAllCookies(); 168 | for (const cookieId in cookies) { 169 | if (!Object.prototype.hasOwnProperty.call(cookies, cookieId)) { 170 | continue; 171 | } 172 | const exportedCookie = cookies[cookieId].cookie; 173 | const url = 'https://' + exportedCookie.domain + exportedCookie.path; 174 | cookieHandler.removeCookie(exportedCookie.name, url); 175 | } 176 | alert('All your cookies were deleted'); 177 | } 178 | 179 | /** 180 | * Export all cookies in the JSON format. 181 | */ 182 | async function exportCookiesAsJson() { 183 | const cookies = await getAllCookies(); 184 | copyText(JsonFormat.format(cookies)); 185 | alert('Done!'); 186 | } 187 | 188 | /** 189 | * Export all cookies in the Netscape format. 190 | */ 191 | async function exportCookiesAsNetscape() { 192 | const cookies = await getAllCookies(); 193 | copyText(NetscapeFormat.format(cookies)); 194 | alert('Done!'); 195 | } 196 | 197 | /** 198 | * Copy some text to the user's clipboard. 199 | * @param {string} text Text to copy. 200 | */ 201 | function copyText(text) { 202 | const fakeText = document.createElement('textarea'); 203 | fakeText.classList.add('clipboardCopier'); 204 | fakeText.textContent = text; 205 | document.body.appendChild(fakeText); 206 | fakeText.focus(); 207 | fakeText.select(); 208 | // TODO: switch to clipboard API. 209 | document.execCommand('Copy'); 210 | document.body.removeChild(fakeText); 211 | } 212 | 213 | /** 214 | * Enables or disables the animations based on the options. 215 | */ 216 | function handleAnimationsEnabled() { 217 | if (optionHandler.getAnimationsEnabled()) { 218 | document.body.classList.remove('notransition'); 219 | } else { 220 | document.body.classList.add('notransition'); 221 | } 222 | } 223 | }); 224 | -------------------------------------------------------------------------------- /interface/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Cookie-Editor Options 12 | 13 | 14 | 15 |
16 |
17 | Options 18 |
19 | 20 | 24 | 30 |
31 | 32 |
33 | 34 | 38 |
39 | When Enabled, Cookie-Editor will be added to the DevTools tabs. 40 | Devtools needs to be restarted to take effect when turning this 41 | option off. 42 |
43 |
44 | 45 |
46 | 47 | 51 |
52 | When Enabled, Cookie-Editor will show transitions and other various 53 | animations in the interface. This is for appearance only. 54 |
55 |
56 | 57 |
58 | 59 | 65 |
66 | Selects the behavior of the export button. When "Ask every time" is 67 | selected, a menu asking you to chose the format will show up when 68 | you press the export button. 69 |
70 |
71 | 72 |
73 | 74 | 86 |
87 | Selects one extra element to display next to the cookie name in the 88 | interface. This can help you identify quickly which cookie you are 89 | looking for. 90 |
91 |
92 | 93 |
94 | 95 | 100 |
101 | When selecting "Auto", Cookie-Editor will match your browser's theme 102 | automatically. 103 |
104 |
105 | 106 |
107 | 108 | 112 |
113 | When enabled, the main button bar will be placed at the top of the 114 | interface instead of the bottom. 115 |
116 |
117 | 118 |
119 | 120 | 124 |
125 | When Enabled, Cookie-Editor will display some small non-intrusive ads at the 126 | top of the main interface. These are used to cover the basic operating 127 | costs of Cookie-Editor. Feel free to disable them, but keep in mind 128 | that I work on Cookie-Editor in free time as a personal project. 129 | You can thank me with a nice review! 130 | 135 |
136 |
137 |
138 | 139 |
140 | All Cookies 141 |
142 | 143 | 144 | 145 | Be careful! Operations in this section will apply to ALL sites. 146 |
147 | Do not share your cookies exported from here to anyone you do not 148 | fully trust. Giving some your cookies this way will give them access 149 | to every account you are currently logged in. 150 |
151 |
152 | Export All Cookies 153 |
154 | 155 | 156 |
157 |
158 | Header string is not available for browser-wide export since it does 159 | not contain information about which site it comes from. 160 |
161 |
162 |
163 | Delete All Cookies 164 | 165 |
166 | This will delete all the cookies on all the site on this browser. 167 | This action is irreversible. Be very careful. 168 |
169 |
170 |
171 | 172 |
173 | About Cookie-Editor 174 |

175 | Cookie-Editor is made by Christophe Gagnier and is fully open source. 176 |

177 |

178 | View the 179 | Source code. Read the 180 | Privacy Policy.
181 | Cookie-Editor is published under 182 | the GPL-3.0 183 | license. 184 |

185 | 191 |

192 | This project is not an official Google project. It is not supported by 193 | Google and Google specifically disclaims all warranties as to its 194 | quality, merchantability, or fitness for a particular purpose. 195 |

196 |
197 |
198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /safari/Cookie-Editor/macOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | --------------------------------------------------------------------------------