├── src ├── svelte.d.ts ├── components │ ├── modules │ │ ├── options │ │ │ └── Options.svelte │ │ ├── popup │ │ │ ├── NavigationTab.svelte │ │ │ ├── Switch.svelte │ │ │ ├── Popup.svelte │ │ │ ├── Settings.svelte │ │ │ └── AdvancedSetting.svelte │ │ └── content │ │ │ ├── LoadingScreen.svelte │ │ │ └── Actions.svelte │ └── icon │ │ ├── Lock.svelte │ │ └── Loading.svelte ├── assets │ └── icons │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon32.png │ │ ├── icon48.png │ │ ├── x.svg │ │ ├── import.svg │ │ ├── info.svg │ │ ├── export.svg │ │ ├── reset.svg │ │ ├── github.svg │ │ └── lock.svg ├── store │ ├── localstorageListener.store.ts │ ├── chat.store.ts │ └── url.store.ts ├── utils │ ├── wait.ts │ ├── openLink.ts │ ├── getElement.ts │ ├── index.ts │ ├── elementVisibility.ts │ ├── changeTextNode.ts │ ├── refreshPage.ts │ ├── BrowserStorage.ts │ ├── findMessageTarget.ts │ ├── logger.ts │ ├── LocalStorage.ts │ └── userAction.ts ├── vite-env.d.ts ├── view │ ├── options │ │ ├── index.ts │ │ └── index.html │ └── popup │ │ ├── index.ts │ │ └── index.html ├── types │ └── Config.d.ts ├── hooks │ ├── useObserver.ts │ ├── useUrl.ts │ ├── useConfig.ts │ ├── useListener.ts │ └── useRender.ts ├── content │ ├── scripts │ │ ├── register.ts │ │ ├── messageParser.ts │ │ └── listeners.ts │ └── index.ts ├── config │ └── index.ts ├── class │ └── Cipher.ts └── browser.d.ts ├── vercel.json ├── .vscode ├── extensions.json └── settings.json ├── docs ├── public │ ├── google7f1de7f44f3b5fd5.html │ ├── favicon.ico │ └── images │ │ ├── action-menu.png │ │ ├── encryption-1.png │ │ ├── appleStore.svg │ │ └── chromeStore.svg ├── .vitepress │ ├── theme │ │ ├── index.ts │ │ └── index.css │ └── config.ts ├── index.md ├── getting-started │ ├── how-to-install.md │ └── how-to-use.md ├── fa │ └── getting-started │ │ ├── how-to-install.md │ │ └── how-to-use.md ├── encryption │ └── introduction.md └── contribution │ ├── getting-started.md │ └── branching.md ├── svelte.config.js ├── .prettierignore ├── .prettierrc ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── build_and_release.yml ├── manifest.json ├── package.json ├── README.md └── LICENSE.md /src/svelte.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svelte"; 2 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true 3 | } 4 | -------------------------------------------------------------------------------- /src/components/modules/options/Options.svelte: -------------------------------------------------------------------------------- 1 |

under development

2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/google7f1de7f44f3b5fd5.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google7f1de7f44f3b5fd5.html -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/docs/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/src/assets/icons/icon128.png -------------------------------------------------------------------------------- /src/assets/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/src/assets/icons/icon16.png -------------------------------------------------------------------------------- /src/assets/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/src/assets/icons/icon32.png -------------------------------------------------------------------------------- /src/assets/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/src/assets/icons/icon48.png -------------------------------------------------------------------------------- /docs/public/images/action-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/docs/public/images/action-menu.png -------------------------------------------------------------------------------- /docs/public/images/encryption-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mostafa-kheibary/ChatGuard/main/docs/public/images/encryption-1.png -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme"; 2 | import "./index.css"; 3 | 4 | export default DefaultTheme; 5 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | }; 6 | -------------------------------------------------------------------------------- /src/store/localstorageListener.store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const listenersStore = writable>({}); 4 | -------------------------------------------------------------------------------- /src/utils/wait.ts: -------------------------------------------------------------------------------- 1 | export const wait = async (ms: number) => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res(); 5 | }, ms); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/store/chat.store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const chatStore = writable({ value: "", encrypted: "", submit: false, loading: false, clickSubmit: false }); 4 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /src/utils/openLink.ts: -------------------------------------------------------------------------------- 1 | export const openLink = (link: string) => { 2 | if (chrome.tabs) return chrome.tabs.create({ url: link }); 3 | if (browser.tabs) browser.tabs.create({ url: link }); 4 | }; 5 | -------------------------------------------------------------------------------- /src/view/options/index.ts: -------------------------------------------------------------------------------- 1 | import Options from "src/components/modules/options/Options.svelte"; 2 | 3 | export const app = new Options({ 4 | target: document.getElementById("app") as HTMLDivElement, 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "json.schemas": [ 3 | { 4 | "fileMatch": ["manifest.json"], 5 | "url": "https://json.schemastore.org/chrome-manifest.json" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/store/url.store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export interface Url { 4 | href: string; // Full path 5 | id: string; // id from id provider 6 | } 7 | export const url = writable({ href: "", id: "" }); 8 | -------------------------------------------------------------------------------- /src/utils/getElement.ts: -------------------------------------------------------------------------------- 1 | export const getElement = async (selector: string) => { 2 | while (document.querySelector(selector) === null) { 3 | await new Promise((resolve) => requestAnimationFrame(resolve)); 4 | } 5 | return document.querySelector(selector) as T; 6 | }; 7 | -------------------------------------------------------------------------------- /src/view/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | options 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "svelteBracketNewLine": false, 6 | "singleQuote": false, 7 | "svelteSortOrder": "scripts-markup-styles", 8 | "svelteStrictMode": false, 9 | "htmlWhitespaceSensitivity": "strict" 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | // vite tsconfig 2 | { 3 | "compilerOptions": { 4 | "composite": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "allowSyntheticDefaultImports": true 9 | }, 10 | "include": ["vite.config.ts", "manifest.json"] 11 | } 12 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Kranky&display=swap"); 2 | @import url("https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100..900&display=swap"); 3 | 4 | *:lang(fa) { 5 | font-family: "Vazirmatn", sans-serif; 6 | } 7 | .content-container:lang(fa) { 8 | direction: rtl; 9 | } 10 | -------------------------------------------------------------------------------- /src/view/popup/index.ts: -------------------------------------------------------------------------------- 1 | import Popup from "src/components/modules/popup/Popup.svelte"; 2 | 3 | import "@material/web/switch/switch"; 4 | import "@material/web/button/filled-button"; 5 | import "@material/web/button/elevated-button"; 6 | 7 | export const app = new Popup({ 8 | target: document.getElementById("app") as HTMLDivElement, 9 | }); 10 | -------------------------------------------------------------------------------- /src/view/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Popup 6 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const getDeviceType = () => { 2 | const ua = navigator.userAgent; 3 | if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) { 4 | return "mobile"; 5 | } 6 | if (/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) { 7 | return "mobile"; 8 | } 9 | return "desktop"; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/elementVisibility.ts: -------------------------------------------------------------------------------- 1 | export const makeElementInvisible = (element: HTMLElement) => { 2 | element.style.visibility = "hidden"; 3 | element.style.position = "absolute"; 4 | element.style.opacity = "0"; 5 | }; 6 | export const makeElementVisible = (element: HTMLElement) => { 7 | element.style.visibility = ""; 8 | element.style.position = ""; 9 | element.style.opacity = ""; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/changeTextNode.ts: -------------------------------------------------------------------------------- 1 | export const changeTextNode = (element: HTMLElement, replace: string) => { 2 | if (element.nodeType === 3 && element.textContent) { 3 | element.textContent = replace; 4 | } else if (element.childNodes.length > 0) { 5 | const firstChild = element.childNodes[0]; 6 | if (firstChild) { 7 | changeTextNode(firstChild as HTMLElement, replace); 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { crx } from "@crxjs/vite-plugin"; 2 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 3 | import { resolve } from "path"; 4 | import { defineConfig } from "vite"; 5 | import manifest from "./manifest.json"; 6 | 7 | const srcDir = resolve(__dirname, "src"); 8 | 9 | export default defineConfig({ 10 | plugins: [svelte(), crx({ manifest })], 11 | resolve: { 12 | alias: { 13 | src: srcDir, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | dist.zip 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | README_MOZILA.md 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | !.vscode/settings.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | # docs 29 | docs/.vitepress/cache -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | 6 | "useDefineForClassFields": true, 7 | "module": "esnext", 8 | "resolveJsonModule": true, 9 | "baseUrl": ".", 10 | "allowJs": true, 11 | "checkJs": true, 12 | "strict": true, 13 | "isolatedModules": true 14 | }, 15 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 16 | "references": [{ "path": "./tsconfig.node.json" }] 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/icons/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/icons/import.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/export.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/types/Config.d.ts: -------------------------------------------------------------------------------- 1 | export type FieldItem = ((device: "desktop" | "mobile") => string) | string; 2 | 3 | export interface Field { 4 | textField: FieldItem; 5 | submitButton: FieldItem; 6 | header: FieldItem; 7 | message: FieldItem; 8 | innerMessageText: FieldItem; 9 | } 10 | export interface Events { 11 | onSubmitClick: FieldItem; 12 | onInput: FieldItem; 13 | } 14 | export interface Selector { 15 | selector: Field; 16 | events: Events; 17 | path: string; 18 | idProvider: string; 19 | } 20 | 21 | export interface Config { 22 | CONTACTS_STORAGE_KEY: string; 23 | ENCRYPT_PREFIX: string; 24 | HANDSHAKE_PREFIX: string; 25 | } 26 | 27 | export interface Contact { 28 | enable: boolean; 29 | publicKey: string; 30 | timeStamp: string; 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/refreshPage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function use to refresh the current page that user viewing from popup and ... 3 | * 4 | * @example 5 | * const { on , onClick } = useListener() 6 | */ 7 | export const refreshPage = () => { 8 | // For Chrome and Edge 9 | if (chrome.tabs) { 10 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { 11 | const currentTab = tabs[0]; 12 | if (currentTab.id) chrome.tabs.reload(currentTab.id); 13 | }); 14 | return; 15 | } 16 | // For Firefox 17 | if (browser.tabs) { 18 | browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { 19 | const currentTab = tabs[0]; 20 | if (currentTab.id) browser.tabs.reload(currentTab.id); 21 | }); 22 | return; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/BrowserStorage.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | guardId: string; 3 | publicKey: string; 4 | privateKey: string; 5 | } 6 | 7 | export type IStorage = { 8 | user: User | null; 9 | enable: boolean; 10 | }; 11 | 12 | const defaultStorage: IStorage = { 13 | user: null, 14 | enable: true, 15 | }; 16 | 17 | async function get() { 18 | if (typeof browser !== "undefined") { 19 | return browser.storage.sync.get(defaultStorage as any) as Promise; 20 | } 21 | return chrome.storage.sync.get(defaultStorage) as Promise; 22 | } 23 | 24 | async function set(value: any) { 25 | if (typeof browser !== "undefined") { 26 | return browser.storage.sync.set(value); 27 | } 28 | return chrome.storage.sync.set(value); 29 | } 30 | 31 | export default { get, set }; 32 | -------------------------------------------------------------------------------- /src/hooks/useObserver.ts: -------------------------------------------------------------------------------- 1 | type CallBackFunction = (mutations: MutationRecord[]) => void; 2 | 3 | /** 4 | * This hook allow you to have a callback on every mutation that targetElement have 5 | * 6 | * @example 7 | * const { onObserve } = useObserver() 8 | */ 9 | const useObserver = () => { 10 | const targetElement = document.body; 11 | const callbacks: CallBackFunction[] = []; 12 | 13 | const onObserve = (callback: CallBackFunction) => { 14 | callbacks.push(callback); 15 | }; 16 | const chatObserver = new MutationObserver((mutations) => { 17 | callbacks.forEach((callback) => { 18 | callback(mutations); 19 | }); 20 | }); 21 | chatObserver.observe(targetElement, { childList: true, subtree: true }); 22 | return { onObserve }; 23 | }; 24 | 25 | export default useObserver; 26 | -------------------------------------------------------------------------------- /src/components/modules/popup/NavigationTab.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | {current} 10 |
11 | 12 | 31 | -------------------------------------------------------------------------------- /src/components/modules/popup/Switch.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | OFF 21 | 22 | ON 23 |
24 | 25 | 33 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: ChatGuard 7 | tagline: ChatGuard is a browser extension designed to enable End to End encryption to your favorite messenger 8 | image: 9 | src: /images/logo.svg 10 | alt: logo 11 | actions: 12 | - theme: brand 13 | text: Download & Install 14 | link: /getting-started/how-to-install 15 | 16 | features: 17 | - title: Supporting major messengers 18 | icon: 💬 19 | details: We support major messengers on the fly, and you can also add support for your own messengers. 20 | - title: Serverless 21 | icon: 🌍 22 | details: Chat Guard doesn't need any external service for encryption; all data will be encrypted on the client. 23 | - title: End to End Encrypted 24 | icon: 🚀 25 | details: Chat Guard utilizes end-to-end encryption with hybrid encryption." 26 | --- 27 | -------------------------------------------------------------------------------- /.github/workflows/build_and_release.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Build and release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build_and_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: ⚙️ Setup node 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: "20.x" 16 | 17 | - name: 🔃 Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: 🛠️ Install and build 21 | run: | 22 | npm install 23 | npm i -g @vercel/ncc 24 | npm run build 25 | 26 | - name: Install zip 📦 27 | uses: montudor/action-zip@v1 28 | 29 | - name: Zip dist files 📦 30 | run: cd dist && zip -r ../dist.zip * 31 | 32 | - name: Create release 📦 33 | uses: "marvinpinto/action-automatic-releases@latest" 34 | with: 35 | repo_token: ${{ secrets.ChatGuard_PAT }} 36 | prerelease: true 37 | files: ./*.zip 38 | -------------------------------------------------------------------------------- /src/utils/findMessageTarget.ts: -------------------------------------------------------------------------------- 1 | export function findTargetRecursive(element: HTMLElement | Node): null | HTMLElement { 2 | if (element.nodeType === 3 && element.textContent?.startsWith("::")) { 3 | return element.parentElement; // Return the parent element 4 | } 5 | // Check child nodes recursively 6 | if (element.childNodes) { 7 | for (let i = 0; i < element.childNodes.length; i++) { 8 | const foundElement = findTargetRecursive(element.childNodes[i]); 9 | if (foundElement) { 10 | return foundElement; // Return the found element 11 | } 12 | } 13 | } 14 | return null; 15 | } 16 | 17 | export function findFirstTextNode(node: any) { 18 | if (node.nodeType === 3) { 19 | return node.textContent.trim(); 20 | } else if (node.nodeType === 1 && node.childNodes.length > 0) { 21 | const firstChild = node.childNodes[0]; 22 | if (firstChild) { 23 | return findFirstTextNode(firstChild); 24 | } 25 | } 26 | return ""; 27 | } 28 | -------------------------------------------------------------------------------- /docs/getting-started/how-to-install.md: -------------------------------------------------------------------------------- 1 | # How to install ChatGuard 2 | 3 | Welcome to Chat Guard for. This guide will walk you through the steps to download and install the Chat Guard on various browsers and devices. 4 | 5 | ## Supported Browser 6 | 7 | 1. [Chrome](#chrome) 8 | 9 | 2. [Mozilla Firefox](#firefox) 10 | 11 | 3. [Kiwi (android browser)](#kiwi) 12 | 13 | ## Chrome 14 | 15 | [![chrome store](/images/chromeStore.svg)](https://chromewebstore.google.com/detail/chatguard-beta/fokigjblcpglhdmjimcpmjikdfmchccg) 16 | 17 | 1. Open the link above and Click on "Add to Chrome". 18 | 19 | ## Firefox 20 | 21 | [![firefox store](/images/firefoxStore.svg)](https://addons.mozilla.org/en-GB/firefox/addon/chatguard/) 22 | 23 | 1. Open the link above and Click on "Add to Firefox". 24 | 25 | ## kiwi 26 | 27 | [![chrome store](/images/chromeStore.svg)](https://chromewebstore.google.com/detail/chatguard-beta/fokigjblcpglhdmjimcpmjikdfmchccg) 28 | 29 | 1. Open the link above and Click on "Add to Chrome". 30 | -------------------------------------------------------------------------------- /src/components/icon/Lock.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 11 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /src/content/scripts/register.ts: -------------------------------------------------------------------------------- 1 | import Cipher from "src/class/Cipher"; 2 | import { config } from "src/config"; 3 | import BrowserStorage from "src/utils/BrowserStorage"; 4 | import LocalStorage from "src/utils/LocalStorage"; 5 | import logger from "src/utils/logger"; 6 | 7 | export async function register() { 8 | let store = await BrowserStorage.get(); 9 | 10 | if (!store.user) { 11 | const { privateKey, publicKey } = await Cipher.generateKeyPair(); 12 | 13 | BrowserStorage.set({ 14 | ...store, 15 | enable: true, 16 | user: { 17 | guardId: crypto.randomUUID(), 18 | publicKey: publicKey.replace(/[\r\n]/g, ""), 19 | privateKey, 20 | }, 21 | }); 22 | logger.info("initial login, private,public key created"); 23 | } 24 | 25 | store = await BrowserStorage.get(); 26 | 27 | LocalStorage.setMap(config.CONTACTS_STORAGE_KEY, "_me_", { 28 | guardId: store.user?.guardId, 29 | publicKey: store.user?.publicKey, 30 | timestamp: new Date().getTime(), 31 | enable: true, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src/hooks/useUrl.ts: -------------------------------------------------------------------------------- 1 | import { url } from "src/store/url.store"; 2 | import { get } from "svelte/store"; 3 | 4 | /** 5 | * a hook that allow you to update url, and get the updated url as object or svelte store 6 | * 7 | * @example 8 | * const { url , urlStore } = useUrl() 9 | */ 10 | const useUrl = (idProvider: string) => { 11 | const urlStore = get(url); 12 | 13 | setInterval(() => { 14 | const urlStore = get(url); 15 | if (urlStore.href === location.href) return; 16 | const href = location.href; 17 | if (idProvider === "#") { 18 | const id = location.hash.slice(1, window.location.hash.length); 19 | return url.set({ id, href }); 20 | } 21 | if (idProvider === "/") { 22 | const id = location.pathname.split("/").at(-1); 23 | if (!id) return; 24 | return url.set({ id, href }); 25 | } 26 | const id = new URLSearchParams(location.search).get(idProvider) || ""; 27 | url.set({ id, href }); 28 | }, 100); 29 | 30 | url.subscribe((newUrlStore) => { 31 | urlStore.href = newUrlStore.href; 32 | urlStore.id = newUrlStore.id; 33 | }); 34 | 35 | return { url, urlStore }; 36 | }; 37 | export default useUrl; 38 | -------------------------------------------------------------------------------- /docs/fa/getting-started/how-to-install.md: -------------------------------------------------------------------------------- 1 | # آموزش نصب چت گارد 2 | 3 | به چت گارد خوش آمدید, داخل این صفحه نحوه نصب افزونه چت گارد را روی پلتفرم های مختلف قرار دادیم. 4 | 5 | ## مرورگرهای پشتیبانی شده 6 | 7 | 1. [کروم](#کروم) 8 | 2. [فایرفاکس](#فایرفاکس) 9 | 3. [کیوی (مرورگر اندروید)](#کیوی) 10 | 11 | ## کروم 12 | 13 | [![chrome store](/images/chromeStore.svg)](https://chromewebstore.google.com/detail/chatguard-beta/fokigjblcpglhdmjimcpmjikdfmchccg) 14 | 15 | 1. لینک بالا را باز کنید و بر روی "Add to Chrome" کلیک کنید. 16 | 17 | ## فایرفاکس 18 | 19 | [![فروشگاه فایرفاکس](/images/firefoxStore.svg)](https://addons.mozilla.org/en-GB/firefox/addon/chatguard/) 20 | 21 | 1. لینک بالا را باز کنید و بر روی "Add to Firefox" کلیک کنید. 22 | 23 | ## کیوی 24 | 25 | 1. فایل افزونه‌ی را از [صفحه دانلود](https://github.com/PrivacyForge/ChatGuard/releases) دانلود کنید. 26 | 27 | 2. مرورگر Kiwi را در دستگاه موبایل خود باز کنید. 28 | 29 | 3. بر روی منو در گوشه بالا و سمت راست کلیک کنید. 30 | 31 | 4. بر روی "افزونه" کلیک کنید. 32 | 33 | 5. مطمئن شوید که "حالت توسعه‌دهنده" فعال است. 34 | 35 | 6. بر روی + (از .zip/...) کلیک کنید. 36 | 37 | 7. فایل zip را که از مرحله 1 دانلود کرده‌اید انتخاب کنید. 38 | -------------------------------------------------------------------------------- /src/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { selectors } from "src/config"; 2 | import type { Events, Field } from "src/types/Config"; 3 | import { getDeviceType } from "src/utils"; 4 | 5 | interface Config { 6 | getSelector: (key: keyof Field) => string; 7 | getEvent: (key: keyof Events) => string; 8 | idProvider: string; 9 | name: string; 10 | } 11 | 12 | export const useConfig = (): Config => { 13 | const type = getDeviceType(); 14 | let host: string = location.hostname; 15 | 16 | const getSelector = (selectorKey: keyof Field) => { 17 | const selector = (selectors[host] as any).selector[selectorKey]; 18 | if (typeof selector === "string") return selector; 19 | return selector(type); 20 | }; 21 | const getEvent = (eventKey: keyof Events) => { 22 | const event = (selectors[host] as any).events[eventKey]; 23 | if (typeof event === "string") return event; 24 | return event(type); 25 | }; 26 | 27 | const key = location.host + location.pathname; 28 | for (const selectorKey in selectors) { 29 | if (selectorKey.startsWith(key)) host = selectorKey; 30 | } 31 | return { 32 | name: host, 33 | idProvider: selectors[host].idProvider, 34 | getEvent, 35 | getSelector, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "ChatGuard-beta", 4 | "description": "Browser extension that allow (End to End) encrypted in web chat messenger", 5 | "version": "0.9.6", 6 | "author": "https://github.com/mostafa-kheibary", 7 | "icons": { 8 | "16": "src/assets/icons/icon16.png", 9 | "32": "src/assets/icons/icon32.png", 10 | "48": "src/assets/icons/icon48.png", 11 | "128": "src/assets/icons/icon128.png" 12 | }, 13 | "content_scripts": [ 14 | { 15 | "matches": [ 16 | "https://web.bale.ai/*", 17 | "https://web.telegram.org/*", 18 | "https://web.splus.ir/*", 19 | "https://web.eitaa.com/*", 20 | "https://web.shad.ir/*", 21 | "https://web.rubika.ir/*", 22 | "https://web.igap.net/*" 23 | ], 24 | "js": ["src/content/index.ts"] 25 | } 26 | ], 27 | "action": { 28 | "default_title": "ChatGuard", 29 | "default_popup": "src/view/popup/index.html" 30 | }, 31 | "options_ui": { 32 | "page": "src/view/options/index.html", 33 | "open_in_tab": true 34 | }, 35 | "browser_specific_settings": { 36 | "gecko": { 37 | "id": "mostafa.kheibary@gmail.com" 38 | } 39 | }, 40 | "permissions": ["storage"] 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | const logStyle = ` 2 | background: #333; 3 | color: #fff; 4 | padding: 4px 8px; 5 | border-radius: 4px; 6 | font-family: 'Arial', sans-serif; 7 | `; 8 | 9 | const timestampStyle = ` 10 | color: #999; 11 | font-size: 10px; 12 | `; 13 | 14 | const getCurrentTimestamp = () => { 15 | const now = new Date(); 16 | return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now 17 | .getSeconds() 18 | .toString() 19 | .padStart(2, "0")}`; 20 | }; 21 | const log = (message: any, style: string = "") => { 22 | if (import.meta.env.MODE !== "development") return; 23 | if (typeof message === "string") 24 | return console.log(`%c[${getCurrentTimestamp()}] %c${message}`, timestampStyle, logStyle.concat(style)); 25 | 26 | console.log(`%c[${getCurrentTimestamp()}]`, timestampStyle, message); 27 | }; 28 | const debug = (message: any) => log(message); 29 | const info = (message: any) => { 30 | const infoStyle = "background:rgba(212,169,28,0.6);color:#fff;"; 31 | log(message, infoStyle); 32 | }; 33 | const error = (message: any) => { 34 | const errorStyle = "background:#d41c1c;color:#fff;"; 35 | log(message, errorStyle); 36 | }; 37 | 38 | export default { info, error, debug }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatguard", 3 | "license": "Apache-2.0", 4 | "author": { 5 | "name": "mostafa kheibary", 6 | "email": "mostafa.kheibary@gmail.com", 7 | "url": "https://github.com/mostafa-kheibary" 8 | }, 9 | "version": "0.9.6", 10 | "type": "module", 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vite build", 14 | "check": "svelte-check --tsconfig ./tsconfig.json", 15 | "docs:dev": "vitepress dev docs", 16 | "docs:build": "vitepress build docs", 17 | "docs:preview": "vitepress preview docs" 18 | }, 19 | "devDependencies": { 20 | "@crxjs/vite-plugin": "2.0.0-beta.23", 21 | "@sveltejs/vite-plugin-svelte": "3.0.2", 22 | "@tsconfig/svelte": "3.0.0", 23 | "@types/chrome": "^0.0.200", 24 | "@types/node-forge": "^1.3.10", 25 | "sass": "^1.63.3", 26 | "svelte": "4.2.12", 27 | "svelte-check": "3.6.4", 28 | "tslib": "2.4.0", 29 | "typescript": "5.3.3", 30 | "vite": "^5.1.4", 31 | "@testing-library/dom": "^9.3.4", 32 | "@testing-library/user-event": "^14.5.2", 33 | "vitepress": "1.0.0-rc.42" 34 | }, 35 | "dependencies": { 36 | "@material/web": "^1.3.0", 37 | "node-forge": "^1.3.1", 38 | "svelte-pathfinder": "^4.7.1", 39 | "worker-timers": "^7.1.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/LocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { get as getStore } from "svelte/store"; 2 | import { listenersStore } from "src/store/localstorageListener.store"; 3 | 4 | function get(key: string) { 5 | return JSON.parse(localStorage.getItem(key) || "null"); 6 | } 7 | 8 | function set(key: string, value: any) { 9 | localStorage.setItem(key, JSON.stringify(value)); 10 | const listeners = getStore(listenersStore); 11 | listeners[key]?.forEach((callback) => { 12 | callback(); 13 | }); 14 | } 15 | 16 | function getMap(key: string, mapKey: string) { 17 | const map = JSON.parse(localStorage.getItem(key) || "{}"); 18 | return map[mapKey] || {}; 19 | } 20 | 21 | function setMap(key: string, mapKey: string, value: any) { 22 | const map = JSON.parse(localStorage.getItem(key) || "{}"); 23 | map[mapKey] = value; 24 | localStorage.setItem(key, JSON.stringify(map)); 25 | const listeners = getStore(listenersStore); 26 | listeners[key]?.forEach((callback) => { 27 | callback(); 28 | }); 29 | } 30 | 31 | function on(key: string, callback: (data: Record) => void) { 32 | listenersStore.update((listeners) => { 33 | listeners[key] = [...(listeners[key] || []), callback]; 34 | return listeners; 35 | }); 36 | } 37 | 38 | export default { get, set, getMap, setMap, on }; 39 | -------------------------------------------------------------------------------- /docs/fa/getting-started/how-to-use.md: -------------------------------------------------------------------------------- 1 | # آموزش استفاده از چت گارد 2 | 3 | در این مرحله، یاد می‌گیریم که چگونه یک گفتگوی امن با گارد چت راه‌اندازی کنیم و از آن به درستی استفاده کنیم. 4 | 5 | ## پیام‌رسان‌های پشتیبانی شده 6 | 7 | - [telegram/k](https://telegram.com/k) 8 | - [telegram/a](https://telegram.com/a) 9 | - [bale](https://web.bale.ai/chat) 10 | - [soroush](https://web.splus.ir/) 11 | - [eitaa](https://web.eitaa.com/) 12 | - [shad](https://web.shad.ir/) 13 | - [rubika](https://web.rubika.ir/) 14 | - [igap](https://web.igap.net/) 15 | 16 | ## ۱. نصب افزونه 17 | 18 | افزونه را با استفاده از [راهنمای نصب](https://github.com/PrivacyForge/ChatGuard/blob/main/docs/getting-started/how-to-install.md) نصب کنید. 19 | 20 | ## ۲. باز کردن یکی از پیام‌رسان‌های پشتیبانی شده 21 | 22 | بعد از نصب موفق، باید یک دکمه قرمز در بالای پیام‌رسان ظاهر شود. 23 | 24 | ![منوی عملیات](/images/action-menu.png) 25 | 26 | ## ۳. انجام دادن هندشیک 27 | 28 | روی دکمه قرمز در یک گفتگو با یک شخص که همچنین افزونه را دارد کلیک کنید. پس از کلیک بر روی منوی عملیات، باید باز شود و می‌توانید یک دکمه "make handshake" را ببینید. شما و شخصی که می‌خواهید هندشیک بدهد باید روی دکمه کلیک کنید. 29 | 30 | ## ۴. شروع گفتگو 31 | 32 | بعد از اتمام موفق هندشیک، دکمه قرمز باید به یک دکمه آبی تبدیل شود و شما قادر به ارسال پیام به دوست خود با رمزگذاری end-to-end خواهید بود. 33 | 34 | همچنین این 35 | -------------------------------------------------------------------------------- /src/components/modules/popup/Popup.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {#if $url !== "/"} 19 | 20 | {/if} 21 | 22 |
23 | 24 | 44 | -------------------------------------------------------------------------------- /docs/getting-started/how-to-use.md: -------------------------------------------------------------------------------- 1 | # How to use ChatGuard 2 | 3 | in this step we learn how to make a secure conversation with ChatGuard and how properly use it. 4 | 5 | ## Supported Messenger 6 | 7 | - [telegram/k](https://telegram.com/k) 8 | - [telegram/a](https://telegram.com/a) 9 | - [bale](https://web.bale.ai/chat) 10 | - [soroush](https://web.splus.ir/) 11 | - [eitaa](https://web.eitaa.com/) 12 | - [shad](https://web.shad.ir/) 13 | - [rubika](https://web.rubika.ir/) 14 | - [igap](https://web.igap.net/) 15 | 16 | ## 1. Install the extension 17 | 18 | install the extension using [installation guid](https://github.com/PrivacyForge/ChatGuard/blob/main/docs/getting-started/how-to-install.md) 19 | 20 | ## 2. Open one of supported messenger 21 | 22 | after a successful installation you should see a red button appear in the top of the messenger 23 | 24 | ![action menu](/images/action-menu.png) 25 | 26 | ## 3. Making a handshake 27 | 28 | click on the red button on a conversation with a person who have the extension as well 29 | after clicking on the action menu, it should open and you can see a "make handshake" button 30 | you and the person that you want to make a handshake with should click on the button 31 | 32 | ## 4. Starts messaging 33 | 34 | after making handshake successfully ended the red button should turn into a blue button 35 | and you be abel to message to your friend with end to end encryption 36 | -------------------------------------------------------------------------------- /src/utils/userAction.ts: -------------------------------------------------------------------------------- 1 | import { useConfig } from "src/hooks/useConfig"; 2 | 3 | // IMPORTANT : temporary disable this feature for other type of input 4 | // function insertElementToDeepestChild(parentNode: HTMLElement, text: string) { 5 | // let currentElement: any = parentNode; 6 | // const textEl = document.createElement("span"); 7 | // textEl.textContent = text; 8 | // textEl.style.display = "none"; 9 | // while (currentElement.children.length > 0) { 10 | // currentElement = currentElement.children[0]; 11 | // } 12 | // currentElement.textContent = ""; 13 | // currentElement.appendChild(textEl); 14 | // } 15 | export const typeTo = async (textFiledSelector: string, message: string) => { 16 | const { getEvent } = useConfig(); 17 | const textFiled = document.querySelector(textFiledSelector) as HTMLElement; 18 | textFiled.focus(); 19 | const textEl = document.createElement("span"); 20 | (textFiled as any).value = message; 21 | textEl.textContent = message; 22 | textEl.style.display = "none"; 23 | textFiled.replaceChildren(textEl); 24 | textFiled.dispatchEvent(new Event(getEvent("onInput"), { cancelable: false, bubbles: true })); 25 | }; 26 | 27 | export const clickTo = (elementSelector: string) => { 28 | const { getEvent } = useConfig(); 29 | const el = document.querySelector(elementSelector) as HTMLElement; 30 | const event = getEvent("onSubmitClick"); 31 | if (event === "click") return el.click(); 32 | el.dispatchEvent(new Event(event, { cancelable: false, bubbles: true })); 33 | }; 34 | -------------------------------------------------------------------------------- /src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/content/scripts/messageParser.ts: -------------------------------------------------------------------------------- 1 | import Cipher from "src/class/Cipher"; 2 | import { config } from "src/config"; 3 | import { useConfig } from "src/hooks/useConfig"; 4 | import type { Url } from "src/store/url.store"; 5 | import { changeTextNode } from "src/utils/changeTextNode"; 6 | import { findFirstTextNode, findTargetRecursive } from "src/utils/findMessageTarget"; 7 | 8 | export const parseMessage = async (urlStore: Url, message: Element, messages: Element[], index: number) => { 9 | const cipher = new Cipher(); 10 | const { getSelector } = useConfig(); 11 | 12 | const targets = Array.from(messages[index].querySelectorAll(getSelector("innerMessageText"))); 13 | const target = targets.find((el) => findTargetRecursive(el)) as HTMLElement | null; 14 | if (!target) return; 15 | 16 | const textNodeContent = findFirstTextNode(target); 17 | 18 | // Messages 19 | if (textNodeContent.startsWith(config.ENCRYPT_PREFIX)) { 20 | changeTextNode(target, "Parsing ..."); 21 | try { 22 | const packet = await cipher.resolveDRSAP(textNodeContent); 23 | if (!packet) { 24 | changeTextNode(target, "⛔ Error in decryption"); 25 | } else { 26 | const status = document.createElement("span"); 27 | status.textContent = "🔒 "; 28 | status.style.opacity = "0.4"; 29 | changeTextNode(target, packet); 30 | target.prepend(status); 31 | } 32 | (target as any).dir = "auto"; 33 | } catch (error) { 34 | changeTextNode(target, "⛔ Error in decryption"); 35 | } 36 | return; 37 | } 38 | // HandShakes 39 | if (textNodeContent.startsWith(config.HANDSHAKE_PREFIX)) { 40 | cipher.resolveDRSAPHandshake(textNodeContent, urlStore.id); 41 | changeTextNode(target, "🤝 encryption Handshake"); 42 | return; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/icon/Loading.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 22 | 23 | 24 | 33 | 42 | 43 | 44 | 53 | 62 | 63 | 64 | 65 | 70 | -------------------------------------------------------------------------------- /docs/encryption/introduction.md: -------------------------------------------------------------------------------- 1 | # Encryption in Chat Guard 2 | 3 | ## Overview 4 | 5 | ![encryption overview](/images/encryption-1.png) 6 | 7 | Our application prioritizes user privacy and security by implementing `end-to-end` encryption. This document outlines the hybrid encryption technique employed, combining both `symmetric` and `asymmetric` encryption methods. 8 | 9 | ## Hybrid Encryption Process 10 | 11 | ### Step 1: `Symmetric` Encryption 12 | 13 | 1.1 **Secret Key Creation:** 14 | 15 | - Generate a secret key for symmetric encryption. 16 | 17 | 1.2 **Message Encryption:** 18 | 19 | - Encrypt the user's message using the generated secret key symmetrically. 20 | 21 | ### Step 2: `Asymmetric` Encryption 22 | 23 | 2.1 **Recipient's Public Key Retrieval:** 24 | 25 | - Obtain the public key of the intended recipient. 26 | 27 | 2.2 **Secret Key Encryption:** 28 | 29 | - Encrypt the generated secret key `symmetrically` with the recipient's public key using `asymmetric` encryption. 30 | 31 | 2.3 **Include Encrypted Secret Key:** 32 | 33 | - Append the encrypted secret key alongside the `symmetrically` encrypted message for transmission. 34 | 35 | ### Step 3: Decryption 36 | 37 | 3.1 **Recipient's Private Key Retrieval:** 38 | 39 | - The recipient, possessing the corresponding private key, retrieves the encrypted secret key. 40 | 41 | 3.2 **Secret Key Decryption:** 42 | 43 | - Decrypt the encrypted secret key `asymmetrically` using the recipient's private key, revealing the original secret key. 44 | 45 | 3.3 **Message Decryption:** 46 | 47 | - With the decrypted secret key, the recipient `symmetrically` decrypts the original message. 48 | 49 | ## Conclusion 50 | 51 | By integrating both symmetric and asymmetric encryption techniques in a hybrid model, Chat Guard ensures a robust and multi-layered approach to protect user data during transmission. This methodology enhances security by combining the efficiency of symmetric encryption for bulk data with the added layer of protection provided by asymmetric encryption for secure key exchange. 52 | -------------------------------------------------------------------------------- /src/hooks/useListener.ts: -------------------------------------------------------------------------------- 1 | import { useConfig } from "./useConfig"; 2 | import useObserver from "./useObserver"; 3 | 4 | /** 5 | * This hook allow you to have event listener on element in a client side rendering app 6 | * it will add a listener to a element no mather if it removed and recreated again 7 | * 8 | * @example 9 | * const { on , onClick } = useListener() 10 | */ 11 | const useListener = () => { 12 | const eventsListener: Record void)[]> = {}; 13 | const clickMap: Record = {}; 14 | const { getEvent } = useConfig(); 15 | const { onObserve } = useObserver(); 16 | 17 | document.addEventListener( 18 | getEvent("onSubmitClick"), 19 | (e) => { 20 | for (const selector in clickMap) { 21 | const el = document.querySelector(selector); 22 | if (el) { 23 | const { top, left, width, height } = el.getBoundingClientRect(); 24 | const { pageX, pageY } = e as MouseEvent; 25 | if (pageX >= left && pageX <= left + width && pageY >= top && pageY <= top + height) { 26 | clickMap[selector].forEach((callback) => callback(e)); 27 | } 28 | } 29 | } 30 | }, 31 | { capture: true } 32 | ); 33 | 34 | const on = (selector: string, name: string, callback: (event: Event) => void) => { 35 | onObserve(() => { 36 | const element = Array.from(document.querySelectorAll(selector)).at(-1); 37 | if (!element) return; 38 | 39 | if ((eventsListener[name] || []).find((cl) => cl === callback)) { 40 | element.removeEventListener(name, callback); 41 | } 42 | element.addEventListener(name, callback, { capture: true }); 43 | 44 | if (!eventsListener[name]) eventsListener[name] = []; 45 | eventsListener[name].push(callback); 46 | }); 47 | }; 48 | const onClick = async (selector: string, callback: Function) => { 49 | if (!clickMap[selector]) clickMap[selector] = []; 50 | clickMap[selector].push(callback); 51 | }; 52 | 53 | return { on, onClick }; 54 | }; 55 | 56 | export default useListener; 57 | -------------------------------------------------------------------------------- /src/content/index.ts: -------------------------------------------------------------------------------- 1 | import Actions from "src/components/modules/content/Actions.svelte"; 2 | import { initLog } from "src/config"; 3 | import Cipher from "src/class/Cipher"; 4 | import LoadingScreen from "src/components/modules/content/LoadingScreen.svelte"; 5 | import useObserver from "src/hooks/useObserver"; 6 | import useRender from "src/hooks/useRender"; 7 | import useUrl from "src/hooks/useUrl"; 8 | import BrowserStorage from "src/utils/BrowserStorage"; 9 | import { getDeviceType } from "src/utils"; 10 | import logger from "src/utils/logger"; 11 | import { parseMessage } from "./scripts/messageParser"; 12 | import { registerEventListener } from "./scripts/listeners"; 13 | import { register } from "./scripts/register"; 14 | import { useConfig } from "src/hooks/useConfig"; 15 | 16 | (async function main() { 17 | let store = await BrowserStorage.get(); 18 | if (!store.enable) return null; 19 | await register(); 20 | const type = getDeviceType(); 21 | const { getSelector, idProvider, name } = useConfig(); 22 | const isTouch = type === "mobile" ? true : false; 23 | if (!name || !idProvider) return logger.error(`config notfound for ${location.hostname}`); 24 | logger.info({ type, isTouch, idProvider, name }); 25 | 26 | const cipher = new Cipher(); 27 | const { onObserve: onRootObserver } = useObserver(); 28 | const { render } = useRender(); 29 | const { urlStore } = useUrl(idProvider); 30 | if (import.meta.env.MODE !== "development") console.log(initLog); 31 | 32 | new LoadingScreen({ target: document.body }); 33 | 34 | render(getSelector("header"), (target, id) => { 35 | // Action Menu on the conversation header 36 | new Actions({ target, props: { cipher, id } }); 37 | }); 38 | // event listener for user action (type,click,sending message) 39 | registerEventListener(urlStore); 40 | 41 | onRootObserver(() => { 42 | // On message receive will run and parse it 43 | const messages = Array.from(document.querySelectorAll(getSelector("message"))); 44 | messages.forEach(async (message, index) => parseMessage(urlStore, message as HTMLElement, messages, index)); 45 | }); 46 | })(); 47 | -------------------------------------------------------------------------------- /src/hooks/useRender.ts: -------------------------------------------------------------------------------- 1 | import { getElement } from "src/utils/getElement"; 2 | import useObserver from "./useObserver"; 3 | import { selectors } from "src/config"; 4 | import { useConfig } from "./useConfig"; 5 | 6 | interface RenderMap { 7 | id: string; 8 | rendered: boolean; 9 | parent: HTMLElement; 10 | patentSelector: string; 11 | render: (parent: HTMLElement, id: string) => void; 12 | } 13 | /** 14 | * a render hook that allow you to render in a page with client side rendering and dont have duplicate element 15 | * 16 | * @example 17 | * const { render } = useRender() 18 | */ 19 | const useRender = () => { 20 | const { name } = useConfig(); 21 | const renderMap: Record = {}; 22 | const { onObserve } = useObserver(); 23 | 24 | onObserve((mutations) => { 25 | mutations.forEach((mutation) => { 26 | mutation.removedNodes.forEach(() => { 27 | for (let els in renderMap) { 28 | const isRendered = document.getElementById(renderMap[els].id); 29 | if (!isRendered) renderMap[els].rendered = false; 30 | } 31 | }); 32 | }); 33 | 34 | for (let els in renderMap) { 35 | const parentElement = document.querySelector(renderMap[els].patentSelector); 36 | const currentPath = "/" + location.pathname.split("/")[1]; 37 | const validPathToRender = selectors[name].path === "*" || currentPath === selectors[name].path; 38 | if (parentElement && !renderMap[els].rendered && validPathToRender) { 39 | renderMap[els].render(parentElement as HTMLElement, els); 40 | renderMap[els].rendered = true; 41 | } 42 | } 43 | }); 44 | 45 | const render = async (targetElement: string, renderCallback: (targetElement: HTMLElement, id: string) => void) => { 46 | const parentElement = await getElement(targetElement); 47 | const id = crypto.randomUUID(); 48 | renderMap[id] = { 49 | id, 50 | rendered: false, 51 | parent: parentElement, 52 | patentSelector: targetElement, 53 | render: renderCallback, 54 | }; 55 | }; 56 | 57 | return { render }; 58 | }; 59 | 60 | export default useRender; 61 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | export default defineConfig({ 4 | title: "ChatGuard", 5 | description: "ChatGuard is a browser extension designed to enable End to End encryption to your favorite messenger", 6 | appearance: "dark", 7 | cleanUrls: true, 8 | lang: "en-US", 9 | sitemap: { 10 | hostname: "https://chat-guard.vercel.app", 11 | }, 12 | head: [ 13 | ["link", { rel: "icon", href: "/images/logo.svg" }], 14 | ["script", { async: "", src: "https://www.googletagmanager.com/gtag/js?id=G-J084XJ7N2C" }], 15 | [ 16 | "script", 17 | {}, 18 | `window.dataLayer = window.dataLayer || []; 19 | function gtag(){dataLayer.push(arguments);} 20 | gtag('js', new Date()); 21 | gtag('config', 'G-J084XJ7N2C');`, 22 | ], 23 | ], 24 | locales: { 25 | root: { 26 | label: "English", 27 | lang: "en", 28 | }, 29 | fa: { 30 | label: "فارسی", 31 | lang: "fa", 32 | link: "/fa", 33 | }, 34 | }, 35 | themeConfig: { 36 | logo: "/images/logo.svg", 37 | siteTitle: "Chat Guard", 38 | nav: [ 39 | { text: "Home", link: "/" }, 40 | { text: "Download & Install", link: "/getting-started/how-to-install" }, 41 | ], 42 | sidebar: [ 43 | { 44 | text: "Getting Started", 45 | items: [ 46 | { 47 | text: "How to install", 48 | link: "/getting-started/how-to-install", 49 | }, 50 | { 51 | text: "How to use", 52 | link: "/getting-started/how-to-use", 53 | }, 54 | ], 55 | }, 56 | { 57 | text: "Encryption", 58 | items: [{ text: "Introduction", link: "/encryption/introduction" }], 59 | }, 60 | { 61 | text: "Contribution", 62 | collapsed: true, 63 | items: [ 64 | { text: "Getting started", link: "/contribution/getting-started" }, 65 | { text: "Branching", link: "/contribution/branching" }, 66 | ], 67 | }, 68 | ], 69 | 70 | socialLinks: [{ icon: "github", link: "https://github.com/PrivacyForge/ChatGuard" }], 71 | 72 | footer: { 73 | message: "Released under the Apache-2.0 License.", 74 | copyright: "Copyright © 2024 ChatGuard developers", 75 | }, 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /docs/contribution/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Contribution Guide 2 | 3 | Thank you for considering contributing to our project! Your contributions are valuable and help improve the overall quality of the project. Before you start, please follow these simple steps: 4 | 5 | ## Using Git Emojis in Your Commits 6 | 7 | We encourage you to use Git emojis in your commit messages to add a touch of expressiveness and clarity. To choose the right emoji for your commits, refer to the [Gitmoji Guide](https://gitmoji.dev/). Simply include the chosen emoji at the beginning of your commit message to convey the nature of your changes. Let's make our commit history more colorful and fun! 🎉 8 | 9 | ### Step 1: Read the GitHub Issues 10 | 11 | Check the project's GitHub repository for any open issues. Issues marked with an "open" flag are available for contributors. Review the issues to find a task or feature you would like to work on. 12 | 13 | ### Step 2: Express Interest 14 | 15 | If you find an issue that you would like to work on, comment on the issue expressing your interest in contributing. This allows the maintainers to coordinate efforts and avoid duplication of work. 16 | 17 | ### Step 3: Confirmation from Maintainer 18 | 19 | Once you express interest, wait for confirmation from the project maintainer. The maintainer will review your interest and either approve or provide guidance on the issue. 20 | 21 | ### Step 4: Branching 22 | 23 | If the maintainer approves your contribution, follow the [branching document](/contribution/branching) rules to create a new branch for your work. This helps keep the main branch clean and organized. 24 | 25 | ### Step 5: Work on the Contribution 26 | 27 | Make the necessary changes and improvements in your branch. Ensure that your code adheres to the project's coding standards and guidelines. 28 | 29 | ### Step 6: Create a Pull Request 30 | 31 | Once your contribution is ready, create a pull request (PR) to merge your changes into the main project. Provide a clear and concise description of the changes you made and the problem you solved. 32 | 33 | ### Step 7: Review 34 | 35 | The maintainers will review your pull request and may provide feedback or request further changes. Be responsive to these comments and make any necessary adjustments. 36 | 37 | ### Step 8: Merge 38 | 39 | Once your pull request passes review and all issues are addressed, the maintainers will merge your changes into the main project. Congratulations, you've successfully contributed to the project! 40 | 41 | Thank you for your contribution! Your efforts are highly appreciated. If you have any questions or need assistance, feel free to reach out to the project maintainers or the community. 42 | -------------------------------------------------------------------------------- /docs/contribution/branching.md: -------------------------------------------------------------------------------- 1 | ## Branching Guide 2 | 3 | When contributing to our project, it's essential to follow a structured approach to branching. This ensures that your changes are well-organized and can be easily integrated into the main codebase. We use a feature branch workflow, which involves creating branches for specific features or fixes. Here's how you can create and manage branches effectively: 4 | 5 | ### Branch Naming Convention 6 | 7 | 1. **Feature Branches**: Create branches from the `develop` branch. Use the following naming convention: 8 | 9 | - `-` 10 | 11 | Example: 12 | 13 | - If you're working on issue #123 and adding a new feature for user authentication, your branch name could be: `123-user-authentication`. 14 | 15 | ### Creating a Branch 16 | 17 | To create a new branch, follow these steps: 18 | 19 | 1. **Fetch the Latest Changes**: Before creating a new branch, ensure your local repository is up to date with the latest changes from the remote repository. 20 | 21 | ```bash 22 | git fetch origin 23 | ``` 24 | 25 | 2. **Checkout the Develop Branch**: Switch to the `develop` branch. 26 | 27 | ```bash 28 | git checkout develop 29 | ``` 30 | 31 | 3. **Create a New Branch**: Create a new branch from the `develop` branch using the naming convention mentioned above. 32 | 33 | ```bash 34 | git checkout -b 123-user-authentication 35 | ``` 36 | 37 | ### Working on Your Branch 38 | 39 | Once you've created your feature branch, you can start working on your changes. Make sure to: 40 | 41 | - Commit your changes regularly, with meaningful commit messages. 42 | - Reference the GitHub issue ID in your commit messages. For example, `Added user authentication feature. Fixes #123`. 43 | 44 | ### Pushing Your Branch 45 | 46 | After making your changes, push your branch to the remote repository: 47 | 48 | ```bash 49 | git push origin 123-user-authentication 50 | ``` 51 | 52 | ### Creating a Merge Request 53 | 54 | Once you've completed your changes and are ready to merge them into the `develop` branch, create a merge request (or pull request) on GitHub. 55 | 56 | Ensure to: 57 | 58 | - Provide a clear description of the changes made. 59 | - Reference the relevant GitHub issue in your pull request description. 60 | - Assign the pull request to the appropriate reviewers or maintainers. 61 | 62 | ### Conclusion 63 | 64 | Following this branching strategy helps maintain a clean and organized codebase, making it easier to collaborate and manage contributions effectively. If you have any questions or need assistance with branching, feel free to reach out to the project maintainers. Thank you for your contribution! 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Logo 5 | 6 |

Chat Guard

7 | Chrome Web Store Users 8 | Chrome Web Store Stars 9 | GitHub License 10 | 11 |
12 |

13 | Simple and easy to use browser extension that allow end to end encryption on web messenger 14 |
15 |
16 | Explore the docs » 17 |
18 |
19 |

20 |
21 | 22 |
23 | 24 | 25 |
26 |
27 | 28 | ## Table of Contents 29 | 30 | - [Table of Contents](#table-of-contents) 31 | - [Features](#features) 32 | - [How Chat Guard Work?](#how-chat-guard-work) 33 | - [License](#license) 34 | - [Donation](#donation) 35 | 36 | ## Features 37 | 38 | - End-to-End Encryption (E2E): Enjoy secure and private conversations without compromising your data. 39 | 40 | - Cross-Application Compatibility: While currently limited to Bale Messenger during the beta phase, ChatGuard aims to extend its support to a wide range of messaging applications. 41 | 42 | - Serverless: No need of server for exchanging public key,chatGuard uses the Messenger that running on as messaging service to transfer public keys. 43 | 44 | ## How Chat Guard Work? 45 | 46 | ChatGuard uses a hybrid encryption method that include a RSA handsake and after that sending every message as encrypted packet with unique secret key that encrypted by the user public key. 47 | 48 | - for more information read the documentation for [how it work](https://chat-guard.vercel.app/encryption/introduction) 49 | 50 | ## License 51 | 52 | Distributed under the Apache-2.0 License. See [Apache-2.0 license](https://github.com/PrivacyForge/ChatGuard/blob/main/LICENSE.md) for more information. 53 | 54 | ## Donation 55 | 56 | I appreciate every donation for this project 57 | every donation will be spend on growing the project and releasing it on different platform like safari, etc. 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/assets/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/content/scripts/listeners.ts: -------------------------------------------------------------------------------- 1 | import Cipher from "src/class/Cipher"; 2 | import { config } from "src/config"; 3 | import useListener from "src/hooks/useListener"; 4 | import { chatStore } from "src/store/chat.store"; 5 | import type { Url } from "src/store/url.store"; 6 | import { getDeviceType } from "src/utils"; 7 | import LocalStorage from "src/utils/LocalStorage"; 8 | import logger from "src/utils/logger"; 9 | import { clickTo, typeTo } from "src/utils/userAction"; 10 | import { wait } from "src/utils/wait"; 11 | import { makeElementInvisible, makeElementVisible } from "src/utils/elementVisibility"; 12 | import { useConfig } from "src/hooks/useConfig"; 13 | 14 | export const registerEventListener = (urlStore: Url) => { 15 | const cipher = new Cipher(); 16 | const type = getDeviceType(); 17 | const { getSelector } = useConfig(); 18 | const isTouch = type === "mobile" ? true : false; 19 | const { onClick } = useListener(); 20 | 21 | const handleSubmitClicked = async (e: Event) => { 22 | const contact = LocalStorage.getMap(config.CONTACTS_STORAGE_KEY, urlStore.id); 23 | let textFieldElement = document.querySelector(getSelector("textField")) as HTMLElement; 24 | const messageLengthIsOk = (textFieldElement.textContent || "").length <= 1200; 25 | 26 | if (!textFieldElement.textContent?.trim() || !contact.enable) return; 27 | 28 | e.preventDefault(); 29 | e.stopImmediatePropagation(); 30 | 31 | if (!messageLengthIsOk) return alert("character length should be bellow 1200 character"); 32 | 33 | makeElementInvisible(textFieldElement); 34 | const encrypted = await cipher.createDRSAP(textFieldElement.textContent || "", urlStore.id); 35 | if (!encrypted) return makeElementVisible(textFieldElement); 36 | 37 | typeTo(getSelector("textField"), encrypted); 38 | await wait(25); 39 | chatStore.update((prev) => ({ ...prev, clickSubmit: true })); 40 | clickTo(getSelector("submitButton")); 41 | makeElementVisible(textFieldElement); 42 | textFieldElement.focus(); 43 | logger.info("Message sent, Send button clicked"); 44 | }; 45 | 46 | const handleTextFieldKeyDown = async (e: KeyboardEvent) => { 47 | // check if we writing text to our textfield or not 48 | let isEqual = (e.target as HTMLElement).isEqualNode(document.querySelector(getSelector("textField"))); 49 | if (!isEqual) return; 50 | 51 | const contact = LocalStorage.getMap(config.CONTACTS_STORAGE_KEY, urlStore.id); 52 | let textFieldElement = document.querySelector(getSelector("textField")) as HTMLElement; 53 | const messageLengthIsOk = (textFieldElement.textContent || "").length <= 1200; 54 | 55 | if (e.key !== "Enter" || !contact.enable || !textFieldElement.textContent?.trim() || e.shiftKey || isTouch) { 56 | return; 57 | } 58 | e.preventDefault(); 59 | e.stopImmediatePropagation(); 60 | 61 | if (!messageLengthIsOk) return alert("character length should be bellow 1200 character"); 62 | makeElementInvisible(textFieldElement); 63 | const encrypted = await cipher.createDRSAP(textFieldElement.textContent || "", urlStore.id); 64 | if (!encrypted) return makeElementVisible(textFieldElement); 65 | 66 | typeTo(getSelector("textField"), encrypted); 67 | await wait(25); 68 | chatStore.update((prev) => ({ ...prev, submit: true })); 69 | clickTo(getSelector("submitButton")); 70 | makeElementVisible(textFieldElement); 71 | textFieldElement.focus(); 72 | logger.info("Message sent, Form submitted"); 73 | }; 74 | 75 | document.addEventListener("keydown", handleTextFieldKeyDown, { capture: true }); 76 | onClick(getSelector("submitButton"), handleSubmitClicked); 77 | }; 78 | -------------------------------------------------------------------------------- /src/components/modules/popup/Settings.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 |
37 |
38 | 39 |

How to use ? openLink("https://chat-guard.vercel.app/getting-started/how-to-use")}>Check here

44 |
45 |
46 | goto("/advanced-setting")}> 47 | 48 | 49 |
50 |
51 | 52 |
53 | logo 54 |

Chat Guard

55 | 56 |
57 | 58 | 67 |
68 | 69 | 128 | -------------------------------------------------------------------------------- /src/components/modules/content/LoadingScreen.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |
27 | {#if !$state.loading && isShow} 28 | 46 |

Handshake Accepted

47 | {:else} 48 | 57 | 58 | 65 | 66 | 67 |

Waiting for Handshake

68 | 69 | {/if} 70 |
71 |
72 | 73 | 129 | -------------------------------------------------------------------------------- /src/components/modules/popup/AdvancedSetting.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 |
77 |
78 |

Config

79 |
80 |

The Config file includes your private key, public key, and Guard ID. You can export/import/reset your keys here 82 | and use them on other devices or for backups. 83 |

84 |
85 | 86 | 87 |
88 | 89 | 90 | Export 91 | 92 | 93 | 94 | Import 95 | 96 |
97 | 98 | 99 | Reset 100 | 101 | {#if error} 102 |

103 | {error} 104 |

105 | {/if} 106 |
107 |
108 | 109 | 158 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import type { Config, Selector } from "src/types/Config"; 2 | 3 | export const initLog = ` 4 | ██████ ██ ██ █████ ████████ 5 | ██ ██ ██ ██ ██ ██ 6 | ██ ███████ ███████ ██ 7 | ██ ██ ██ ██ ██ ██ 8 | ██████ ██ ██ ██ ██ ██ 9 | 10 | 11 | ██████ ██ ██ █████ ██████ ██████ 12 | ██ ██ ██ ██ ██ ██ ██ ██ ██ 13 | ██ ███ ██ ██ ███████ ██████ ██ ██ 14 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 15 | ██████ ██████ ██ ██ ██ ██ ██████ 16 | 17 | `; 18 | 19 | export const version = "V1"; 20 | export const config: Config = { 21 | CONTACTS_STORAGE_KEY: "chatguard_contacts", 22 | ENCRYPT_PREFIX: `::CGM_${version}::`, 23 | HANDSHAKE_PREFIX: `::HSH_${version}::`, 24 | }; 25 | export const selectors: Record = { 26 | "web.bale.ai": { 27 | selector: { 28 | textField: (type) => (type === "mobile" ? "#main-message-input" : "#editable-message-text"), 29 | submitButton: (type) => 30 | type === "mobile" 31 | ? "#chat_footer > :has(#main-message-input) :nth-child(5)" 32 | : "#chat_footer > :has(#editable-message-text) :nth-child(5)", 33 | header: "#toolbarWrapper", 34 | message: "[data-sid]", 35 | innerMessageText: "span", 36 | }, 37 | events: { 38 | onSubmitClick: "click", 39 | onInput: "input", 40 | }, 41 | path: "*", 42 | idProvider: "uid", 43 | }, 44 | "web.telegram.org/k/": { 45 | selector: { 46 | textField: ".input-message-input[data-peer-id][contenteditable]", 47 | submitButton: ".btn-send-container button", 48 | header: ".chat .sidebar-header", 49 | message: ".chat [data-peer-id][data-mid]", 50 | innerMessageText: ".message", 51 | }, 52 | events: { 53 | onSubmitClick: (type) => (type === "mobile" ? "mousedown" : "click"), 54 | onInput: "input", 55 | }, 56 | path: "*", 57 | idProvider: "#", 58 | }, 59 | "web.splus.ir": { 60 | selector: { 61 | textField: "#editable-message-text", 62 | header: "#MiddleColumn > div.messages-layout > div.MiddleHeader", 63 | message: "[data-message-id]", 64 | innerMessageText: ".contWrap", 65 | submitButton: 66 | "#MiddleColumn > div.messages-layout > div.Transition.slide > div > div.middle-column-footer > div > button", 67 | }, 68 | events: { 69 | onSubmitClick: "click", 70 | onInput: "input", 71 | }, 72 | path: "*", 73 | idProvider: "#", 74 | }, 75 | "web.telegram.org/a/": { 76 | selector: { 77 | textField: "#editable-message-text", 78 | header: "#MiddleColumn > div.messages-layout > div.MiddleHeader", 79 | message: "[data-message-id]", 80 | innerMessageText: ".text-content", 81 | submitButton: 82 | "#MiddleColumn > div.messages-layout > div.Transition > div > div.middle-column-footer > div.Composer.shown.mounted > button", 83 | }, 84 | events: { 85 | onSubmitClick: "click", 86 | onInput: "input", 87 | }, 88 | path: "*", 89 | idProvider: "#", 90 | }, 91 | "web.eitaa.com": { 92 | selector: { 93 | textField: ".chats-container .input-message-input", 94 | header: ".chats-container .sidebar-header", 95 | message: ".bubble[data-mid]", 96 | innerMessageText: ".message", 97 | submitButton: ".btn-send-container button", 98 | }, 99 | events: { 100 | onSubmitClick: (type) => (type === "mobile" ? "mousedown" : "click"), 101 | onInput: "input", 102 | }, 103 | path: "*", 104 | idProvider: "#", 105 | }, 106 | "web.shad.ir": { 107 | selector: { 108 | textField: ".input-message-input [contenteditable]", 109 | header: ".chat-info-container", 110 | message: "[data-msg-id]", 111 | innerMessageText: ".message [rb-message-text] div", 112 | submitButton: ".btn-send-container button .c-ripple", 113 | }, 114 | events: { 115 | onSubmitClick: "click", 116 | onInput: "keyup", 117 | }, 118 | path: "*", 119 | idProvider: "#", 120 | }, 121 | "web.rubika.ir": { 122 | selector: { 123 | textField: ".input-message-input [contenteditable]", 124 | header: ".chat-info-container", 125 | message: "[data-msg-id]", 126 | innerMessageText: ".message [rb-message-text] div", 127 | submitButton: ".btn-send-container button .c-ripple", 128 | }, 129 | events: { 130 | onSubmitClick: "click", 131 | onInput: "keyup", 132 | }, 133 | path: "*", 134 | idProvider: "#", 135 | }, 136 | "web.igap.net": { 137 | selector: { 138 | textField: "#textarea_ref[contenteditable]", 139 | header: "header > div", 140 | message: "[data-message-id]", 141 | innerMessageText: "[data-message-id] > div [dir]", 142 | submitButton: "main > :last-child > :last-child", 143 | }, 144 | events: { 145 | onSubmitClick: "click", 146 | onInput: "input", 147 | }, 148 | path: "/app", 149 | idProvider: "q", 150 | }, 151 | "twitter.com": { 152 | selector: { 153 | textField: "[contenteditable]", 154 | header: "[role=main] > div > div > div > :nth-child(2) > div > div > div > div > div > div > div", 155 | message: "[data-testid=messageEntry] > div > :nth-child(2) [role=presentation]", 156 | innerMessageText: "span", 157 | submitButton: "[role=complementary] > :nth-child(2) > [role=button]", 158 | }, 159 | events: { 160 | onSubmitClick: "click", 161 | onInput: "input", 162 | }, 163 | path: "/messages", 164 | idProvider: "/", 165 | }, 166 | }; 167 | -------------------------------------------------------------------------------- /src/class/Cipher.ts: -------------------------------------------------------------------------------- 1 | import forge from "node-forge"; 2 | import { config } from "src/config"; 3 | import BrowserStorage from "src/utils/BrowserStorage"; 4 | import LocalStorage from "src/utils/LocalStorage"; 5 | import logger from "src/utils/logger"; 6 | 7 | export class Cipher { 8 | public async createDRSAP(message: string, to: string) { 9 | if (message.trim() === "") return null; 10 | 11 | let store = await BrowserStorage.get(); 12 | const secretKey = forge.random.getBytesSync(16); 13 | const hexSecret = forge.util.bytesToHex(secretKey); 14 | 15 | const ownPublicKey = forge.pki.publicKeyFromPem(store.user!.publicKey); 16 | const { publicKey: publicKeyPem } = LocalStorage.getMap(config.CONTACTS_STORAGE_KEY, to); 17 | if (!publicKeyPem) return null; 18 | const toPublicKey = forge.pki.publicKeyFromPem(publicKeyPem); 19 | const r1 = forge.util.bytesToHex(ownPublicKey.encrypt(secretKey)); 20 | const r2 = forge.util.bytesToHex(toPublicKey.encrypt(secretKey)); 21 | const encryptedMessage = await this.encryptAES(message, hexSecret); 22 | const template = config.ENCRYPT_PREFIX + r1 + r2 + encryptedMessage; 23 | return template; 24 | } 25 | public async resolveDRSAP(packet: string) { 26 | let store = await BrowserStorage.get(); 27 | const packetArray = packet.split(config.ENCRYPT_PREFIX)[1].split(""); 28 | const r1 = packetArray.splice(0, 128).join(""); 29 | const r2 = packetArray.splice(0, 128).join(""); 30 | 31 | const ownPrivateKey = forge.pki.privateKeyFromPem(store.user!.privateKey); 32 | let key; 33 | try { 34 | key = ownPrivateKey.decrypt(forge.util.hexToBytes(r1)); 35 | } catch (error) {} 36 | if (!key) { 37 | try { 38 | key = ownPrivateKey.decrypt(forge.util.hexToBytes(r2)); 39 | } catch (error) {} 40 | } 41 | if (!key) return null; 42 | const message = this.decryptAES(packetArray.join(""), forge.util.bytesToHex(key)); 43 | return message; 44 | } 45 | 46 | public async createDRSAPHandshake(to: string) { 47 | const store = await BrowserStorage.get(); 48 | const cleanedPublicKey = store.user!.publicKey.replace(/[\r\n]/g, ""); 49 | const timestamp = new Date().getTime().toString(); 50 | const packet = 51 | config.HANDSHAKE_PREFIX + btoa(store.user!.guardId) + cleanedPublicKey + timestamp.length + timestamp + btoa(to); 52 | return packet; 53 | } 54 | public async resolveDRSAPHandshake(packet: string, forId: string) { 55 | const store = await BrowserStorage.get(); 56 | const handshakeArray = packet.split(config.HANDSHAKE_PREFIX)[1].split(""); 57 | const guardId = atob(handshakeArray.splice(0, 48).join("")); 58 | const publicKey = handshakeArray.splice(0, 178).join(""); 59 | const timestampLength = handshakeArray.splice(0, 2).join(""); 60 | const timestamp = handshakeArray.splice(0, +timestampLength).join(""); 61 | const toId = atob(handshakeArray.join("")); 62 | 63 | if (store.user?.guardId === guardId || forId === "") return; 64 | const oldContact = LocalStorage.getMap(config.CONTACTS_STORAGE_KEY, forId); 65 | if (+timestamp < +(oldContact.timestamp || 0)) return logger.debug(`Handshake ${toId} is old`); 66 | const allHandshakes = LocalStorage.get(config.CONTACTS_STORAGE_KEY); 67 | let isFound = false; 68 | for (let handshake in allHandshakes) { 69 | if (publicKey === allHandshakes[handshake].publicKey) isFound = true; 70 | } 71 | if (isFound) { 72 | logger.info(`Already have Handshake ${toId}`); 73 | if (!oldContact.publicKey) return; 74 | LocalStorage.setMap(config.CONTACTS_STORAGE_KEY, forId, { 75 | ...oldContact, 76 | }); 77 | return; 78 | } 79 | 80 | LocalStorage.setMap(config.CONTACTS_STORAGE_KEY, forId, { 81 | publicKey, 82 | timestamp, 83 | enable: true, 84 | }); 85 | logger.info(`New Handshake ${toId} registered`); 86 | return true; 87 | } 88 | 89 | public static validatePublicPem(pem: string) { 90 | try { 91 | forge.pki.publicKeyFromPem(pem); 92 | return true; 93 | } catch (error) { 94 | return false; 95 | } 96 | } 97 | public static validatePrivatePem(pem: string) { 98 | try { 99 | forge.pki.privateKeyFromPem(pem); 100 | return true; 101 | } catch (error) { 102 | return false; 103 | } 104 | } 105 | public async encryptAES(message: string, secretKey: string): Promise { 106 | const encoder = new TextEncoder(); 107 | const data = encoder.encode(message); 108 | 109 | const cryptoKey = await window.crypto.subtle.importKey( 110 | "raw", 111 | encoder.encode(secretKey), 112 | { name: "AES-CBC" }, 113 | false, 114 | ["encrypt"] 115 | ); 116 | 117 | const iv = window.crypto.getRandomValues(new Uint8Array(16)); 118 | 119 | const encryptedData = await window.crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, cryptoKey, data); 120 | 121 | const encryptedMessage = new Uint8Array(iv.length + encryptedData.byteLength); 122 | encryptedMessage.set(iv); 123 | encryptedMessage.set(new Uint8Array(encryptedData), iv.length); 124 | 125 | return Array.from(encryptedMessage) 126 | .map((byte) => ("0" + (byte & 0xff).toString(16)).slice(-2)) 127 | .join(""); 128 | } 129 | 130 | public async decryptAES(encryptedMessage: string, secretKey: string): Promise { 131 | const decoder = new TextDecoder(); 132 | const encryptedData = new Uint8Array(encryptedMessage.match(/[\da-f]{2}/gi)!.map((hex) => parseInt(hex, 16))); 133 | 134 | const iv = encryptedData.slice(0, 16); 135 | 136 | const cryptoKey = await window.crypto.subtle.importKey( 137 | "raw", 138 | new TextEncoder().encode(secretKey), 139 | { name: "AES-CBC" }, 140 | false, 141 | ["decrypt"] 142 | ); 143 | 144 | const decryptedData = await window.crypto.subtle.decrypt( 145 | { name: "AES-CBC", iv: iv }, 146 | cryptoKey, 147 | encryptedData.slice(16) 148 | ); 149 | 150 | return decoder.decode(decryptedData); 151 | } 152 | 153 | static async generateKeyPair() { 154 | const keyPair = await forge.pki.rsa.generateKeyPair({ bits: 512, workers: 2 }); 155 | const publicKey = forge.pki.publicKeyToPem(keyPair.publicKey); 156 | const privateKey = forge.pki.privateKeyToPem(keyPair.privateKey); 157 | 158 | return { privateKey, publicKey }; 159 | } 160 | } 161 | export default Cipher; 162 | -------------------------------------------------------------------------------- /src/components/modules/content/Actions.svelte: -------------------------------------------------------------------------------- 1 | 88 | 89 | 90 | 91 |
92 | 98 |
104 | {#if status === "safe"} 105 |
106 |
107 | Enable 108 |
109 | {/if} 110 |
111 | 112 | 113 | {status === "safe" ? "retry" : "make"} HandShake 114 | 115 |
116 |
117 |
118 | 119 | 218 | -------------------------------------------------------------------------------- /docs/public/images/appleStore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 mostafa kheibary 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/public/images/chromeStore.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 | -------------------------------------------------------------------------------- /src/browser.d.ts: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // license, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | interface EvListener { 6 | addListener: (callback: T) => void; 7 | removeListener: (listener: T) => void; 8 | hasListener: (listener: T) => boolean; 9 | } 10 | 11 | type Listener = EvListener<(arg: T) => void>; 12 | 13 | declare namespace browser.alarms { 14 | type Alarm = { 15 | name: string; 16 | scheduledTime: number; 17 | periodInMinutes?: number; 18 | }; 19 | 20 | type When = { 21 | when?: number; 22 | periodInMinutes?: number; 23 | }; 24 | type DelayInMinutes = { 25 | delayInMinutes?: number; 26 | periodInMinutes?: number; 27 | }; 28 | function create(name?: string, alarmInfo?: When | DelayInMinutes): void; 29 | function get(name?: string): Promise; 30 | function getAll(): Promise; 31 | function clear(name?: string): Promise; 32 | function clearAll(): Promise; 33 | 34 | const onAlarm: Listener; 35 | } 36 | 37 | declare namespace browser.bookmarks { 38 | type BookmarkTreeNodeUnmodifiable = "managed"; 39 | type BookmarkTreeNodeType = "bookmark" | "folder" | "separator"; 40 | type BookmarkTreeNode = { 41 | id: string; 42 | parentId?: string; 43 | index?: number; 44 | url?: string; 45 | title: string; 46 | dateAdded?: number; 47 | dateGroupModified?: number; 48 | unmodifiable?: BookmarkTreeNodeUnmodifiable; 49 | children?: BookmarkTreeNode[]; 50 | type?: BookmarkTreeNodeType; 51 | }; 52 | 53 | type CreateDetails = { 54 | parentId?: string; 55 | index?: number; 56 | title?: string; 57 | type?: BookmarkTreeNodeType; 58 | url?: string; 59 | }; 60 | 61 | function create(bookmark: CreateDetails): Promise; 62 | function get(idOrIdList: string | string[]): Promise; 63 | function getChildren(id: string): Promise; 64 | function getRecent(numberOfItems: number): Promise; 65 | function getSubTree(id: string): Promise<[BookmarkTreeNode]>; 66 | function getTree(): Promise<[BookmarkTreeNode]>; 67 | 68 | type Destination = 69 | | { 70 | parentId: string; 71 | index?: number; 72 | } 73 | | { 74 | index: number; 75 | parentId?: string; 76 | }; 77 | function move(id: string, destination: Destination): Promise; 78 | function remove(id: string): Promise; 79 | function removeTree(id: string): Promise; 80 | function search( 81 | query: 82 | | string 83 | | { 84 | query?: string; 85 | url?: string; 86 | title?: string; 87 | } 88 | ): Promise; 89 | function update(id: string, changes: { title: string; url: string }): Promise; 90 | 91 | const onCreated: EvListener<(id: string, bookmark: BookmarkTreeNode) => void>; 92 | const onRemoved: EvListener< 93 | ( 94 | id: string, 95 | removeInfo: { 96 | parentId: string; 97 | index: number; 98 | node: BookmarkTreeNode; 99 | } 100 | ) => void 101 | >; 102 | const onChanged: EvListener< 103 | ( 104 | id: string, 105 | changeInfo: { 106 | title: string; 107 | url?: string; 108 | } 109 | ) => void 110 | >; 111 | const onMoved: EvListener< 112 | ( 113 | id: string, 114 | moveInfo: { 115 | parentId: string; 116 | index: number; 117 | oldParentId: string; 118 | oldIndex: number; 119 | } 120 | ) => void 121 | >; 122 | } 123 | 124 | declare namespace browser.browserAction { 125 | type ColorArray = [number, number, number, number]; 126 | type ImageDataType = ImageData; 127 | 128 | function setTitle(details: { title: string | null; tabId?: number }): void; 129 | function getTitle(details: { tabId?: number }): Promise; 130 | 131 | type IconViaPath = { 132 | path: string | { [size: number]: string }; 133 | tabId?: number; 134 | }; 135 | 136 | type IconViaImageData = { 137 | imageData: ImageDataType | { [size: number]: ImageDataType }; 138 | tabId?: number; 139 | }; 140 | 141 | type IconReset = { 142 | imageData?: {} | null; 143 | path?: {} | null; 144 | tabId?: number; 145 | }; 146 | 147 | function setIcon(details: IconViaPath | IconViaImageData | IconReset): Promise; 148 | function setPopup(details: { popup: string | null; tabId?: number }): void; 149 | function getPopup(details: { tabId?: number }): Promise; 150 | function openPopup(): Promise; 151 | function setBadgeText(details: { text: string | null; tabId?: number }): void; 152 | function getBadgeText(details: { tabId?: number }): Promise; 153 | function setBadgeBackgroundColor(details: { color: string | ColorArray | null; tabId?: number }): void; 154 | function getBadgeBackgroundColor(details: { tabId?: number }): Promise; 155 | function setBadgeTextColor(details: { color: string | ColorArray; tabId?: number }): void; 156 | function setBadgeTextColor(details: { color: string | ColorArray; windowId?: number }): void; 157 | function setBadgeTextColor(details: { color: null; tabId?: number }): void; 158 | function getBadgeTextColor(details: { tabId?: string }): Promise; 159 | function getBadgeTextColor(details: { windowId?: string }): Promise; 160 | function enable(tabId?: number): void; 161 | function disable(tabId?: number): void; 162 | 163 | const onClicked: Listener; 164 | } 165 | 166 | declare namespace browser.browsingData { 167 | type DataTypeSet = { 168 | cache?: boolean; 169 | cookies?: boolean; 170 | downloads?: boolean; 171 | fileSystems?: boolean; 172 | formData?: boolean; 173 | history?: boolean; 174 | indexedDB?: boolean; 175 | localStorage?: boolean; 176 | passwords?: boolean; 177 | pluginData?: boolean; 178 | serverBoundCertificates?: boolean; 179 | serviceWorkers?: boolean; 180 | }; 181 | 182 | type DataRemovalOptions = { 183 | since?: number; 184 | originTypes?: { unprotectedWeb: boolean }; 185 | }; 186 | 187 | type ExtraDataRemovalOptions = { 188 | hostnames?: string[]; 189 | }; 190 | 191 | function remove(removalOptions: DataRemovalOptions, dataTypes: DataTypeSet): Promise; 192 | function removeCache(removalOptions?: DataRemovalOptions): Promise; 193 | function removeCookies(removalOptions: DataRemovalOptions & ExtraDataRemovalOptions): Promise; 194 | function removeLocalStorage(removalOptions: DataRemovalOptions & ExtraDataRemovalOptions): Promise; 195 | function removeDownloads(removalOptions: DataRemovalOptions): Promise; 196 | function removeFormData(removalOptions: DataRemovalOptions): Promise; 197 | function removeHistory(removalOptions: DataRemovalOptions): Promise; 198 | function removePasswords(removalOptions: DataRemovalOptions): Promise; 199 | function removePluginData(removalOptions: DataRemovalOptions): Promise; 200 | function settings(): Promise<{ 201 | options: DataRemovalOptions; 202 | dataToRemove: DataTypeSet; 203 | dataRemovalPermitted: DataTypeSet; 204 | }>; 205 | } 206 | 207 | declare namespace browser.commands { 208 | type Command = { 209 | name?: string; 210 | description?: string; 211 | shortcut?: string; 212 | }; 213 | 214 | function getAll(): Promise; 215 | 216 | const onCommand: Listener; 217 | } 218 | 219 | declare namespace browser.menus { 220 | type ContextType = 221 | | "all" 222 | | "audio" 223 | | "bookmarks" 224 | | "browser_action" 225 | | "editable" 226 | | "frame" 227 | | "image" 228 | // | "launcher" unsupported 229 | | "link" 230 | | "page" 231 | | "page_action" 232 | | "password" 233 | | "selection" 234 | | "tab" 235 | | "tools_menu" 236 | | "video"; 237 | 238 | type ItemType = "normal" | "checkbox" | "radio" | "separator"; 239 | 240 | type OnClickData = { 241 | bookmarkId?: string; 242 | checked?: boolean; 243 | editable: boolean; 244 | frameId?: number; 245 | frameUrl?: string; 246 | linkText?: string; 247 | linkUrl?: string; 248 | mediaType?: string; 249 | menuItemId: number | string; 250 | modifiers: string[]; 251 | pageUrl?: string; 252 | parentMenuItemId?: number | string; 253 | selectionText?: string; 254 | srcUrl?: string; 255 | targetElementId?: number; 256 | wasChecked?: boolean; 257 | }; 258 | 259 | const ACTION_MENU_TOP_LEVEL_LIMIT: number; 260 | 261 | function create( 262 | createProperties: { 263 | checked?: boolean; 264 | command?: "_execute_browser_action" | "_execute_page_action" | "_execute_sidebar_action"; 265 | contexts?: ContextType[]; 266 | documentUrlPatterns?: string[]; 267 | enabled?: boolean; 268 | icons?: object; 269 | id?: string; 270 | onclick?: (info: OnClickData, tab: browser.tabs.Tab) => void; 271 | parentId?: number | string; 272 | targetUrlPatterns?: string[]; 273 | title?: string; 274 | type?: ItemType; 275 | visible?: boolean; 276 | }, 277 | callback?: () => void 278 | ): number | string; 279 | 280 | function getTargetElement(targetElementId: number): object | null; 281 | 282 | function refresh(): Promise; 283 | 284 | function remove(menuItemId: number | string): Promise; 285 | 286 | function removeAll(): Promise; 287 | 288 | function update( 289 | id: number | string, 290 | updateProperties: { 291 | checked?: boolean; 292 | command?: "_execute_browser_action" | "_execute_page_action" | "_execute_sidebar_action"; 293 | contexts?: ContextType[]; 294 | documentUrlPatterns?: string[]; 295 | enabled?: boolean; 296 | onclick?: (info: OnClickData, tab: browser.tabs.Tab) => void; 297 | parentId?: number | string; 298 | targetUrlPatterns?: string[]; 299 | title?: string; 300 | type?: ItemType; 301 | visible?: boolean; 302 | } 303 | ): Promise; 304 | 305 | const onClicked: EvListener<(info: OnClickData, tab: browser.tabs.Tab) => void>; 306 | 307 | const onHidden: EvListener<() => void>; 308 | 309 | const onShown: EvListener<(info: OnClickData, tab: browser.tabs.Tab) => void>; 310 | } 311 | 312 | declare namespace browser.contextualIdentities { 313 | type IdentityColor = "blue" | "turquoise" | "green" | "yellow" | "orange" | "red" | "pink" | "purple"; 314 | type IdentityIcon = "fingerprint" | "briefcase" | "dollar" | "cart" | "circle"; 315 | 316 | type ContextualIdentity = { 317 | cookieStoreId: string; 318 | color: IdentityColor; 319 | icon: IdentityIcon; 320 | name: string; 321 | }; 322 | 323 | function create(details: { name: string; color: IdentityColor; icon: IdentityIcon }): Promise; 324 | function get(cookieStoreId: string): Promise; 325 | function query(details: { name?: string }): Promise; 326 | function update( 327 | cookieStoreId: string, 328 | details: { 329 | name: string; 330 | color: IdentityColor; 331 | icon: IdentityIcon; 332 | } 333 | ): Promise; 334 | function remove(cookieStoreId: string): Promise; 335 | } 336 | 337 | declare namespace browser.cookies { 338 | type Cookie = { 339 | name: string; 340 | value: string; 341 | domain: string; 342 | hostOnly: boolean; 343 | path: string; 344 | secure: boolean; 345 | httpOnly: boolean; 346 | session: boolean; 347 | firstPartyDomain?: string; 348 | sameSite: SameSiteStatus; 349 | expirationDate?: number; 350 | storeId: string; 351 | }; 352 | 353 | type CookieStore = { 354 | id: string; 355 | incognito: boolean; 356 | tabIds: number[]; 357 | }; 358 | 359 | type SameSiteStatus = "no_restriction" | "lax" | "strict"; 360 | 361 | type OnChangedCause = "evicted" | "expired" | "explicit" | "expired_overwrite" | "overwrite"; 362 | 363 | function get(details: { 364 | url: string; 365 | name: string; 366 | storeId?: string; 367 | firstPartyDomain?: string; 368 | }): Promise; 369 | function getAll(details: { 370 | url?: string; 371 | name?: string; 372 | domain?: string; 373 | path?: string; 374 | secure?: boolean; 375 | session?: boolean; 376 | storeId?: string; 377 | firstPartyDomain?: string; 378 | }): Promise; 379 | function set(details: { 380 | domain?: string; 381 | expirationDate?: number; 382 | firstPartyDomain?: string; 383 | httpOnly?: boolean; 384 | name?: string; 385 | path?: string; 386 | sameSite?: SameSiteStatus; 387 | secure?: boolean; 388 | storeId?: string; 389 | url: string; 390 | value?: string; 391 | }): Promise; 392 | function remove(details: { 393 | url: string; 394 | name: string; 395 | storeId?: string; 396 | firstPartyDomain?: string; 397 | }): Promise; 398 | function getAllCookieStores(): Promise; 399 | 400 | const onChanged: Listener<{ 401 | removed: boolean; 402 | cookie: Cookie; 403 | cause: OnChangedCause; 404 | }>; 405 | } 406 | 407 | declare namespace browser.contentScripts { 408 | type RegisteredContentScriptOptions = { 409 | allFrames?: boolean; 410 | css?: ({ file: string } | { code: string })[]; 411 | excludeGlobs?: string[]; 412 | excludeMatches?: string[]; 413 | includeGlobs?: string[]; 414 | js?: ({ file: string } | { code: string })[]; 415 | matchAboutBlank?: boolean; 416 | matches: string[]; 417 | runAt?: "document_start" | "document_end" | "document_idle"; 418 | }; 419 | 420 | type RegisteredContentScript = { 421 | unregister: () => void; 422 | }; 423 | 424 | function register(contentScriptOptions: RegisteredContentScriptOptions): Promise; 425 | } 426 | 427 | declare namespace browser.devtools.inspectedWindow { 428 | const tabId: number; 429 | 430 | function eval( 431 | expression: string 432 | ): Promise<[any, { isException: boolean; value: string } | { isError: boolean; code: string }]>; 433 | 434 | function reload(reloadOptions?: { ignoreCache?: boolean; userAgent?: string; injectedScript?: string }): void; 435 | } 436 | 437 | declare namespace browser.devtools.network { 438 | const onNavigated: Listener; 439 | } 440 | 441 | declare namespace browser.devtools.panels { 442 | type ExtensionPanel = { 443 | onShown: Listener; 444 | onHidden: Listener; 445 | }; 446 | 447 | function create(title: string, iconPath: string, pagePath: string): Promise; 448 | } 449 | 450 | declare namespace browser.downloads { 451 | type FilenameConflictAction = "uniquify" | "overwrite" | "prompt"; 452 | 453 | type InterruptReason = 454 | | "FILE_FAILED" 455 | | "FILE_ACCESS_DENIED" 456 | | "FILE_NO_SPACE" 457 | | "FILE_NAME_TOO_LONG" 458 | | "FILE_TOO_LARGE" 459 | | "FILE_VIRUS_INFECTED" 460 | | "FILE_TRANSIENT_ERROR" 461 | | "FILE_BLOCKED" 462 | | "FILE_SECURITY_CHECK_FAILED" 463 | | "FILE_TOO_SHORT" 464 | | "NETWORK_FAILED" 465 | | "NETWORK_TIMEOUT" 466 | | "NETWORK_DISCONNECTED" 467 | | "NETWORK_SERVER_DOWN" 468 | | "NETWORK_INVALID_REQUEST" 469 | | "SERVER_FAILED" 470 | | "SERVER_NO_RANGE" 471 | | "SERVER_BAD_CONTENT" 472 | | "SERVER_UNAUTHORIZED" 473 | | "SERVER_CERT_PROBLEM" 474 | | "SERVER_FORBIDDEN" 475 | | "USER_CANCELED" 476 | | "USER_SHUTDOWN" 477 | | "CRASH"; 478 | 479 | type DangerType = "file" | "url" | "content" | "uncommon" | "host" | "unwanted" | "safe" | "accepted"; 480 | 481 | type State = "in_progress" | "interrupted" | "complete"; 482 | 483 | type DownloadItem = { 484 | id: number; 485 | url: string; 486 | referrer: string; 487 | filename: string; 488 | incognito: boolean; 489 | danger: string; 490 | mime: string; 491 | startTime: string; 492 | endTime?: string; 493 | estimatedEndTime?: string; 494 | state: string; 495 | paused: boolean; 496 | canResume: boolean; 497 | error?: string; 498 | bytesReceived: number; 499 | totalBytes: number; 500 | fileSize: number; 501 | exists: boolean; 502 | byExtensionId?: string; 503 | byExtensionName?: string; 504 | }; 505 | 506 | type Delta = { 507 | current?: T; 508 | previous?: T; 509 | }; 510 | 511 | type StringDelta = Delta; 512 | type DoubleDelta = Delta; 513 | type BooleanDelta = Delta; 514 | type DownloadTime = Date | string | number; 515 | 516 | type DownloadQuery = { 517 | query?: string[]; 518 | startedBefore?: DownloadTime; 519 | startedAfter?: DownloadTime; 520 | endedBefore?: DownloadTime; 521 | endedAfter?: DownloadTime; 522 | totalBytesGreater?: number; 523 | totalBytesLess?: number; 524 | filenameRegex?: string; 525 | urlRegex?: string; 526 | limit?: number; 527 | orderBy?: string; 528 | id?: number; 529 | url?: string; 530 | filename?: string; 531 | danger?: DangerType; 532 | mime?: string; 533 | startTime?: string; 534 | endTime?: string; 535 | state?: State; 536 | paused?: boolean; 537 | error?: InterruptReason; 538 | bytesReceived?: number; 539 | totalBytes?: number; 540 | fileSize?: number; 541 | exists?: boolean; 542 | }; 543 | 544 | function download(options: { 545 | url: string; 546 | filename?: string; 547 | conflictAction?: string; 548 | saveAs?: boolean; 549 | method?: string; 550 | headers?: { [key: string]: string }; 551 | body?: string; 552 | }): Promise; 553 | function search(query: DownloadQuery): Promise; 554 | function pause(downloadId: number): Promise; 555 | function resume(downloadId: number): Promise; 556 | function cancel(downloadId: number): Promise; 557 | // unsupported: function getFileIcon(downloadId: number, options?: { size?: number }): 558 | // Promise; 559 | function open(downloadId: number): Promise; 560 | function show(downloadId: number): Promise; 561 | function showDefaultFolder(): void; 562 | function erase(query: DownloadQuery): Promise; 563 | function removeFile(downloadId: number): Promise; 564 | // unsupported: function acceptDanger(downloadId: number): Promise; 565 | // unsupported: function drag(downloadId: number): Promise; 566 | // unsupported: function setShelfEnabled(enabled: boolean): void; 567 | 568 | const onCreated: Listener; 569 | const onErased: Listener; 570 | const onChanged: Listener<{ 571 | id: number; 572 | url?: StringDelta; 573 | filename?: StringDelta; 574 | danger?: StringDelta; 575 | mime?: StringDelta; 576 | startTime?: StringDelta; 577 | endTime?: StringDelta; 578 | state?: StringDelta; 579 | canResume?: BooleanDelta; 580 | paused?: BooleanDelta; 581 | error?: StringDelta; 582 | totalBytes?: DoubleDelta; 583 | fileSize?: DoubleDelta; 584 | exists?: BooleanDelta; 585 | }>; 586 | } 587 | 588 | declare namespace browser.events { 589 | type UrlFilter = { 590 | hostContains?: string; 591 | hostEquals?: string; 592 | hostPrefix?: string; 593 | hostSuffix?: string; 594 | pathContains?: string; 595 | pathEquals?: string; 596 | pathPrefix?: string; 597 | pathSuffix?: string; 598 | queryContains?: string; 599 | queryEquals?: string; 600 | queryPrefix?: string; 601 | querySuffix?: string; 602 | urlContains?: string; 603 | urlEquals?: string; 604 | urlMatches?: string; 605 | originAndPathMatches?: string; 606 | urlPrefix?: string; 607 | urlSuffix?: string; 608 | schemes?: string[]; 609 | ports?: Array; 610 | }; 611 | } 612 | 613 | declare namespace browser.extension { 614 | type ViewType = "tab" | "notification" | "popup"; 615 | 616 | const lastError: string | null; 617 | const inIncognitoContext: boolean; 618 | 619 | function getURL(path: string): string; 620 | function getViews(fetchProperties?: { type?: ViewType; windowId?: number }): Window[]; 621 | function getBackgroundPage(): Window; 622 | function isAllowedIncognitoAccess(): Promise; 623 | function isAllowedFileSchemeAccess(): Promise; 624 | // unsupported: events as they are deprecated 625 | } 626 | 627 | declare namespace browser.extensionTypes { 628 | type ImageFormat = "jpeg" | "png"; 629 | type ImageDetails = { 630 | format: ImageFormat; 631 | quality: number; 632 | }; 633 | type RunAt = "document_start" | "document_end" | "document_idle"; 634 | type InjectDetails = { 635 | allFrames?: boolean; 636 | code?: string; 637 | file?: string; 638 | frameId?: number; 639 | matchAboutBlank?: boolean; 640 | runAt?: RunAt; 641 | }; 642 | type InjectDetailsCSS = InjectDetails & { cssOrigin?: "user" | "author" }; 643 | } 644 | 645 | declare namespace browser.find { 646 | type FindOptions = { 647 | tabid: number; 648 | caseSensitive: boolean; 649 | entireWord: boolean; 650 | includeRangeData: boolean; 651 | includeRectData: boolean; 652 | }; 653 | 654 | type FindResults = { 655 | count: number; 656 | rangeData?: RangeData[]; 657 | rectData?: RectData[]; 658 | }; 659 | 660 | type RangeData = { 661 | framePos: number; 662 | startTextNodePos: number; 663 | endTextNodePos: number; 664 | startOffset: number; 665 | endOffset: number; 666 | text: string; 667 | }; 668 | 669 | type RectData = { 670 | rectsAndTexts: RectsAndTexts; 671 | text: string; 672 | }; 673 | 674 | type RectsAndTexts = { 675 | rectList: RectItem[]; 676 | textList: string[]; 677 | }; 678 | 679 | type RectItem = { 680 | top: number; 681 | left: number; 682 | bottom: number; 683 | right: number; 684 | }; 685 | 686 | function find(query: string, object?: FindOptions): Promise; 687 | function highlightResults(): void; 688 | function removeHighlighting(): void; 689 | } 690 | 691 | declare namespace browser.history { 692 | type TransitionType = 693 | | "link" 694 | | "typed" 695 | | "auto_bookmark" 696 | | "auto_subframe" 697 | | "manual_subframe" 698 | | "generated" 699 | | "auto_toplevel" 700 | | "form_submit" 701 | | "reload" 702 | | "keyword" 703 | | "keyword_generated"; 704 | 705 | type HistoryItem = { 706 | id: string; 707 | url?: string; 708 | title?: string; 709 | lastVisitTime?: number; 710 | visitCount?: number; 711 | typedCount?: number; 712 | }; 713 | 714 | type VisitItem = { 715 | id: string; 716 | visitId: string; 717 | visitTime?: number; 718 | refferingVisitId: string; 719 | transition: TransitionType; 720 | }; 721 | 722 | function search(query: { 723 | text: string; 724 | startTime?: number | string | Date; 725 | endTime?: number | string | Date; 726 | maxResults?: number; 727 | }): Promise; 728 | 729 | function getVisits(details: { url: string }): Promise; 730 | 731 | function addUrl(details: { 732 | url: string; 733 | title?: string; 734 | transition?: TransitionType; 735 | visitTime?: number | string | Date; 736 | }): Promise; 737 | 738 | function deleteUrl(details: { url: string }): Promise; 739 | 740 | function deleteRange(range: { startTime: number | string | Date; endTime: number | string | Date }): Promise; 741 | 742 | function deleteAll(): Promise; 743 | 744 | const onVisited: Listener; 745 | 746 | // TODO: Ensure that urls is not `urls: [string]` instead 747 | const onVisitRemoved: Listener<{ allHistory: boolean; urls: string[] }>; 748 | } 749 | 750 | declare namespace browser.i18n { 751 | type LanguageCode = string; 752 | 753 | function getAcceptLanguages(): Promise; 754 | 755 | function getMessage(messageName: string, substitutions?: string | string[]): string; 756 | 757 | function getUILanguage(): LanguageCode; 758 | 759 | function detectLanguage(text: string): Promise<{ 760 | isReliable: boolean; 761 | languages: { language: LanguageCode; percentage: number }[]; 762 | }>; 763 | } 764 | 765 | declare namespace browser.identity { 766 | function getRedirectURL(): string; 767 | function launchWebAuthFlow(details: { url: string; interactive: boolean }): Promise; 768 | } 769 | 770 | declare namespace browser.idle { 771 | type IdleState = "active" | "idle" /* unsupported: | "locked" */; 772 | 773 | function queryState(detectionIntervalInSeconds: number): Promise; 774 | function setDetectionInterval(intervalInSeconds: number): void; 775 | 776 | const onStateChanged: Listener; 777 | } 778 | 779 | declare namespace browser.management { 780 | type ExtensionInfo = { 781 | description: string; 782 | // unsupported: disabledReason: string, 783 | enabled: boolean; 784 | homepageUrl: string; 785 | hostPermissions: string[]; 786 | icons: { size: number; url: string }[]; 787 | id: string; 788 | installType: "admin" | "development" | "normal" | "sideload" | "other"; 789 | mayDisable: boolean; 790 | name: string; 791 | // unsupported: offlineEnabled: boolean, 792 | optionsUrl: string; 793 | permissions: string[]; 794 | shortName: string; 795 | // unsupported: type: string, 796 | updateUrl: string; 797 | version: string; 798 | // unsupported: versionName: string, 799 | }; 800 | 801 | function getSelf(): Promise; 802 | function uninstallSelf(options: { showConfirmDialog: boolean; dialogMessage: string }): Promise; 803 | } 804 | 805 | declare namespace browser.notifications { 806 | type TemplateType = "basic" /* | "image" | "list" | "progress" */; 807 | 808 | type NotificationOptions = { 809 | type: TemplateType; 810 | message: string; 811 | title: string; 812 | iconUrl?: string; 813 | }; 814 | 815 | function create(id: string | null, options: NotificationOptions): Promise; 816 | function create(options: NotificationOptions): Promise; 817 | 818 | function clear(id: string): Promise; 819 | 820 | function getAll(): Promise<{ [key: string]: NotificationOptions }>; 821 | 822 | const onClosed: Listener; 823 | 824 | const onClicked: Listener; 825 | } 826 | 827 | declare namespace browser.omnibox { 828 | type OnInputEnteredDisposition = "currentTab" | "newForegroundTab" | "newBackgroundTab"; 829 | type SuggestResult = { 830 | content: string; 831 | description: string; 832 | }; 833 | 834 | function setDefaultSuggestion(suggestion: { description: string }): void; 835 | 836 | const onInputStarted: Listener; 837 | const onInputChanged: EvListener<(text: string, suggest: (arg: SuggestResult[]) => void) => void>; 838 | const onInputEntered: EvListener<(text: string, disposition: OnInputEnteredDisposition) => void>; 839 | const onInputCancelled: Listener; 840 | } 841 | 842 | declare namespace browser.pageAction { 843 | type ImageDataType = ImageData; 844 | 845 | function show(tabId: number): void; 846 | 847 | function hide(tabId: number): void; 848 | 849 | function setTitle(details: { tabId: number; title: string }): void; 850 | 851 | function getTitle(details: { tabId: number }): Promise; 852 | 853 | function setIcon(details: { tabId: number; path?: string | object; imageData?: ImageDataType }): Promise; 854 | 855 | function setPopup(details: { tabId: number; popup: string }): void; 856 | 857 | function getPopup(details: { tabId: number }): Promise; 858 | 859 | const onClicked: Listener; 860 | } 861 | 862 | declare namespace browser.permissions { 863 | type Permission = 864 | | "activeTab" 865 | | "alarms" 866 | | "background" 867 | | "bookmarks" 868 | | "browsingData" 869 | | "browserSettings" 870 | | "clipboardRead" 871 | | "clipboardWrite" 872 | | "contextMenus" 873 | | "contextualIdentities" 874 | | "cookies" 875 | | "downloads" 876 | | "downloads.open" 877 | | "find" 878 | | "geolocation" 879 | | "history" 880 | | "identity" 881 | | "idle" 882 | | "management" 883 | | "menus" 884 | | "nativeMessaging" 885 | | "notifications" 886 | | "pkcs11" 887 | | "privacy" 888 | | "proxy" 889 | | "sessions" 890 | | "storage" 891 | | "tabs" 892 | | "theme" 893 | | "topSites" 894 | | "unlimitedStorage" 895 | | "webNavigation" 896 | | "webRequest" 897 | | "webRequestBlocking"; 898 | 899 | type Permissions = { 900 | origins?: string[]; 901 | permissions?: Permission[]; 902 | }; 903 | 904 | function contains(permissions: Permissions): Promise; 905 | 906 | function getAll(): Promise; 907 | 908 | function remove(permissions: Permissions): Promise; 909 | 910 | function request(permissions: Permissions): Promise; 911 | 912 | // Not yet support in Edge and Firefox: 913 | // const onAdded: Listener; 914 | // const onRemoved: Listener; 915 | } 916 | 917 | declare namespace browser.runtime { 918 | const lastError: string | null; 919 | const id: string; 920 | 921 | type Port = { 922 | name: string; 923 | disconnect(): void; 924 | error: object; 925 | onDisconnect: Listener; 926 | onMessage: Listener; 927 | postMessage: (message: T) => void; 928 | sender?: runtime.MessageSender; 929 | }; 930 | 931 | type MessageSender = { 932 | tab?: browser.tabs.Tab; 933 | frameId?: number; 934 | id?: string; 935 | url?: string; 936 | tlsChannelId?: string; 937 | }; 938 | 939 | type PlatformOs = "mac" | "win" | "android" | "cros" | "linux" | "openbsd"; 940 | type PlatformArch = "arm" | "x86-32" | "x86-64"; 941 | type PlatformNaclArch = "arm" | "x86-32" | "x86-64"; 942 | 943 | type PlatformInfo = { 944 | os: PlatformOs; 945 | arch: PlatformArch; 946 | }; 947 | 948 | // type RequestUpdateCheckStatus = "throttled" | "no_update" | "update_available"; 949 | type OnInstalledReason = "install" | "update" | "chrome_update" | "shared_module_update"; 950 | type OnRestartRequiredReason = "app_update" | "os_update" | "periodic"; 951 | 952 | type FirefoxSpecificProperties = { 953 | id?: string; 954 | strict_min_version?: string; 955 | strict_max_version?: string; 956 | update_url?: string; 957 | }; 958 | 959 | type IconPath = { [urlName: string]: string } | string; 960 | 961 | type Manifest = { 962 | // Required 963 | manifest_version: 2; 964 | name: string; 965 | version: string; 966 | /** Required in Microsoft Edge */ 967 | author?: string; 968 | 969 | // Optional 970 | 971 | // ManifestBase 972 | description?: string; 973 | homepage_url?: string; 974 | short_name?: string; 975 | 976 | // WebExtensionManifest 977 | background?: { 978 | page: string; 979 | scripts: string[]; 980 | persistent?: boolean; 981 | }; 982 | content_scripts?: { 983 | matches: string[]; 984 | exclude_matches?: string[]; 985 | include_globs?: string[]; 986 | exclude_globs?: string[]; 987 | css?: string[]; 988 | js?: string[]; 989 | all_frames?: boolean; 990 | match_about_blank?: boolean; 991 | run_at?: "document_start" | "document_end" | "document_idle"; 992 | }[]; 993 | content_security_policy?: string; 994 | developer?: { 995 | name?: string; 996 | url?: string; 997 | }; 998 | icons?: { 999 | [imgSize: string]: string; 1000 | }; 1001 | incognito?: "spanning" | "split" | "not_allowed"; 1002 | optional_permissions?: browser.permissions.Permission[]; 1003 | options_ui?: { 1004 | page: string; 1005 | browser_style?: boolean; 1006 | chrome_style?: boolean; 1007 | open_in_tab?: boolean; 1008 | }; 1009 | permissions?: browser.permissions.Permission[]; 1010 | web_accessible_resources?: string[]; 1011 | 1012 | // WebExtensionLangpackManifest 1013 | languages: { 1014 | [langCode: string]: { 1015 | chrome_resources: { 1016 | [resName: string]: string | { [urlName: string]: string }; 1017 | }; 1018 | version: string; 1019 | }; 1020 | }; 1021 | langpack_id?: string; 1022 | sources?: { 1023 | [srcName: string]: { 1024 | base_path: string; 1025 | paths?: string[]; 1026 | }; 1027 | }; 1028 | 1029 | // Extracted from components 1030 | browser_action?: { 1031 | default_title?: string; 1032 | default_icon?: IconPath; 1033 | theme_icons?: { 1034 | light: string; 1035 | dark: string; 1036 | size: number; 1037 | }[]; 1038 | default_popup?: string; 1039 | browser_style?: boolean; 1040 | default_area?: "navbar" | "menupanel" | "tabstrip" | "personaltoolbar"; 1041 | }; 1042 | commands?: { 1043 | [keyName: string]: { 1044 | suggested_key?: { 1045 | default?: string; 1046 | mac?: string; 1047 | linux?: string; 1048 | windows?: string; 1049 | chromeos?: string; 1050 | android?: string; 1051 | ios?: string; 1052 | }; 1053 | description?: string; 1054 | }; 1055 | }; 1056 | default_locale?: browser.i18n.LanguageCode; 1057 | devtools_page?: string; 1058 | omnibox?: { 1059 | keyword: string; 1060 | }; 1061 | page_action?: { 1062 | default_title?: string; 1063 | default_icon?: IconPath; 1064 | default_popup?: string; 1065 | browser_style?: boolean; 1066 | show_matches?: string[]; 1067 | hide_matches?: string[]; 1068 | }; 1069 | sidebar_action?: { 1070 | default_panel: string; 1071 | default_title?: string; 1072 | default_icon?: IconPath; 1073 | browser_style?: boolean; 1074 | }; 1075 | 1076 | // Firefox specific 1077 | applications?: { 1078 | gecko?: FirefoxSpecificProperties; 1079 | }; 1080 | browser_specific_settings?: { 1081 | gecko?: FirefoxSpecificProperties; 1082 | }; 1083 | experiment_apis?: any; 1084 | protocol_handlers?: { 1085 | name: string; 1086 | protocol: string; 1087 | uriTemplate: string; 1088 | }; 1089 | 1090 | // Opera specific 1091 | minimum_opera_version?: string; 1092 | 1093 | // Chrome specific 1094 | action?: any; 1095 | automation?: any; 1096 | background_page?: any; 1097 | chrome_settings_overrides?: { 1098 | homepage?: string; 1099 | search_provider?: { 1100 | name: string; 1101 | search_url: string; 1102 | keyword?: string; 1103 | favicon_url?: string; 1104 | suggest_url?: string; 1105 | instant_url?: string; 1106 | is_default?: string; 1107 | image_url?: string; 1108 | search_url_post_params?: string; 1109 | instant_url_post_params?: string; 1110 | image_url_post_params?: string; 1111 | alternate_urls?: string[]; 1112 | prepopulated_id?: number; 1113 | }; 1114 | }; 1115 | chrome_ui_overrides?: { 1116 | bookmarks_ui?: { 1117 | remove_bookmark_shortcut?: true; 1118 | remove_button?: true; 1119 | }; 1120 | }; 1121 | chrome_url_overrides?: { 1122 | newtab?: string; 1123 | bookmarks?: string; 1124 | history?: string; 1125 | }; 1126 | content_capabilities?: any; 1127 | converted_from_user_script?: any; 1128 | current_locale?: any; 1129 | declarative_net_request?: any; 1130 | event_rules?: any[]; 1131 | export?: { 1132 | whitelist?: string[]; 1133 | }; 1134 | externally_connectable?: { 1135 | ids?: string[]; 1136 | matches?: string[]; 1137 | accepts_tls_channel_id?: boolean; 1138 | }; 1139 | file_browser_handlers?: { 1140 | id: string; 1141 | default_title: string; 1142 | file_filters: string[]; 1143 | }[]; 1144 | file_system_provider_capabilities?: { 1145 | source: "file" | "device" | "network"; 1146 | configurable?: boolean; 1147 | multiple_mounts?: boolean; 1148 | watchable?: boolean; 1149 | }; 1150 | import?: { 1151 | id: string; 1152 | minimum_version?: string; 1153 | }[]; 1154 | input_components?: any; 1155 | key?: string; 1156 | minimum_chrome_version?: string; 1157 | nacl_modules?: { 1158 | path: string; 1159 | mime_type: string; 1160 | }[]; 1161 | oauth2?: any; 1162 | offline_enabled?: boolean; 1163 | options_page?: string; 1164 | platforms?: any; 1165 | requirements?: any; 1166 | sandbox?: { 1167 | pages: string[]; 1168 | content_security_policy?: string; 1169 | }[]; 1170 | signature?: any; 1171 | spellcheck?: any; 1172 | storage?: { 1173 | managed_schema: string; 1174 | }; 1175 | system_indicator?: any; 1176 | tts_engine?: { 1177 | voice: { 1178 | voice_name: string; 1179 | lang?: string; 1180 | gender?: "male" | "female"; 1181 | event_types: ("start" | "word" | "sentence" | "marker" | "end" | "error")[]; 1182 | }[]; 1183 | }; 1184 | update_url?: string; 1185 | version_name?: string; 1186 | }; 1187 | 1188 | function getBackgroundPage(): Promise; 1189 | function openOptionsPage(): Promise; 1190 | function getManifest(): Manifest; 1191 | 1192 | function getURL(path: string): string; 1193 | function setUninstallURL(url: string): Promise; 1194 | function reload(): void; 1195 | // Will not exist: https://bugzilla.mozilla.org/show_bug.cgi?id=1314922 1196 | // function RequestUpdateCheck(): Promise; 1197 | function connect(connectInfo?: { name?: string; includeTlsChannelId?: boolean }): Port; 1198 | function connect(extensionId?: string, connectInfo?: { name?: string; includeTlsChannelId?: boolean }): Port; 1199 | function connectNative(application: string): Port; 1200 | 1201 | function sendMessage(message: T): Promise; 1202 | function sendMessage( 1203 | message: T, 1204 | options: { includeTlsChannelId?: boolean; toProxyScript?: boolean } 1205 | ): Promise; 1206 | function sendMessage(extensionId: string, message: T): Promise; 1207 | function sendMessage( 1208 | extensionId: string, 1209 | message: T, 1210 | options?: { includeTlsChannelId?: boolean; toProxyScript?: boolean } 1211 | ): Promise; 1212 | 1213 | function sendNativeMessage(application: string, message: object): Promise; 1214 | function getPlatformInfo(): Promise; 1215 | function getBrowserInfo(): Promise<{ 1216 | name: string; 1217 | vendor: string; 1218 | version: string; 1219 | buildID: string; 1220 | }>; 1221 | // Unsupported: https://bugzilla.mozilla.org/show_bug.cgi?id=1339407 1222 | // function getPackageDirectoryEntry(): Promise; 1223 | 1224 | const onStartup: Listener; 1225 | const onInstalled: Listener<{ 1226 | reason: OnInstalledReason; 1227 | previousVersion?: string; 1228 | id?: string; 1229 | }>; 1230 | // Unsupported 1231 | // const onSuspend: Listener; 1232 | // const onSuspendCanceled: Listener; 1233 | // const onBrowserUpdateAvailable: Listener; 1234 | // const onRestartRequired: Listener; 1235 | const onUpdateAvailable: Listener<{ version: string }>; 1236 | const onConnect: Listener; 1237 | 1238 | const onConnectExternal: Listener; 1239 | 1240 | type onMessagePromise = ( 1241 | message: object, 1242 | sender: MessageSender, 1243 | sendResponse: (response: object) => boolean 1244 | ) => Promise; 1245 | 1246 | type onMessageBool = ( 1247 | message: object, 1248 | sender: MessageSender, 1249 | sendResponse: (response: object) => Promise 1250 | ) => boolean; 1251 | 1252 | type onMessageVoid = ( 1253 | message: object, 1254 | sender: MessageSender, 1255 | sendResponse: (response: object) => Promise 1256 | ) => void; 1257 | 1258 | type onMessageEvent = onMessagePromise | onMessageBool | onMessageVoid; 1259 | const onMessage: EvListener; 1260 | 1261 | const onMessageExternal: EvListener; 1262 | } 1263 | 1264 | declare namespace browser.sessions { 1265 | type Filter = { maxResults?: number }; 1266 | 1267 | type Session = { 1268 | lastModified: number; 1269 | tab: browser.tabs.Tab; 1270 | window: browser.windows.Window; 1271 | }; 1272 | 1273 | const MAX_SESSION_RESULTS: number; 1274 | 1275 | function getRecentlyClosed(filter?: Filter): Promise; 1276 | 1277 | function restore(sessionId: string): Promise; 1278 | 1279 | function setTabValue(tabId: number, key: string, value: string | object): Promise; 1280 | 1281 | function getTabValue(tabId: number, key: string): Promise; 1282 | 1283 | function removeTabValue(tabId: number, key: string): Promise; 1284 | 1285 | function setWindowValue(windowId: number, key: string, value: string | object): Promise; 1286 | 1287 | function getWindowValue(windowId: number, key: string): Promise; 1288 | 1289 | function removeWindowValue(windowId: number, key: string): Promise; 1290 | 1291 | const onChanged: EvListener<() => void>; 1292 | } 1293 | 1294 | declare namespace browser.sidebarAction { 1295 | type ImageDataType = ImageData; 1296 | 1297 | function setPanel(details: { panel: string; tabId?: number }): void; 1298 | 1299 | function getPanel(details: { tabId?: number }): Promise; 1300 | 1301 | function setTitle(details: { title: string; tabId?: number }): void; 1302 | 1303 | function getTitle(details: { tabId?: number }): Promise; 1304 | 1305 | type IconViaPath = { 1306 | path: string | { [index: number]: string }; 1307 | tabId?: number; 1308 | }; 1309 | 1310 | type IconViaImageData = { 1311 | imageData: ImageDataType | { [index: number]: ImageDataType }; 1312 | tabId?: number; 1313 | }; 1314 | 1315 | function setIcon(details: IconViaPath | IconViaImageData): Promise; 1316 | 1317 | function open(): Promise; 1318 | 1319 | function close(): Promise; 1320 | } 1321 | 1322 | declare namespace browser.storage { 1323 | // Non-firefox implementations don't accept all these types 1324 | type StorageValue = 1325 | | string 1326 | | number 1327 | | boolean 1328 | | null 1329 | | undefined 1330 | | RegExp 1331 | | ArrayBuffer 1332 | | Uint8ClampedArray 1333 | | Uint8Array 1334 | | Uint16Array 1335 | | Uint32Array 1336 | | Int8Array 1337 | | Int16Array 1338 | | Int32Array 1339 | | Float32Array 1340 | | Float64Array 1341 | | DataView 1342 | | StorageArray 1343 | | StorageMap 1344 | | StorageSet 1345 | | StorageObject; 1346 | 1347 | // The Index signature makes casting to/from classes or interfaces a pain. 1348 | // Custom types are OK. 1349 | interface StorageObject { 1350 | [key: string]: StorageValue; 1351 | } 1352 | // These have to be interfaces rather than types to avoid a circular 1353 | // definition of StorageValue 1354 | interface StorageArray extends Array {} 1355 | interface StorageMap extends Map {} 1356 | interface StorageSet extends Set {} 1357 | 1358 | interface Get { 1359 | (keys?: string | string[] | null): Promise; 1360 | /* (keys: T): Promise<{[K in keyof T]: T[K]}>; */ 1361 | (keys: T): Promise; 1362 | } 1363 | 1364 | type StorageArea = { 1365 | get: Get; 1366 | // unsupported: getBytesInUse: (keys: string|string[]|null) => Promise, 1367 | set: (keys: StorageObject) => Promise; 1368 | remove: (keys: string | string[]) => Promise; 1369 | clear: () => Promise; 1370 | }; 1371 | 1372 | type StorageChange = { 1373 | oldValue?: any; 1374 | newValue?: any; 1375 | }; 1376 | 1377 | const sync: StorageArea; 1378 | const local: StorageArea; 1379 | // unsupported: const managed: StorageArea; 1380 | 1381 | type ChangeDict = { [field: string]: StorageChange }; 1382 | type StorageName = "sync" | "local" /* |"managed" */; 1383 | 1384 | const onChanged: EvListener<(changes: ChangeDict, areaName: StorageName) => void>; 1385 | } 1386 | 1387 | declare namespace browser.tabs { 1388 | type MutedInfoReason = "capture" | "extension" | "user"; 1389 | type MutedInfo = { 1390 | muted: boolean; 1391 | extensionId?: string; 1392 | reason: MutedInfoReason; 1393 | }; 1394 | // TODO: Specify PageSettings properly. 1395 | type PageSettings = object; 1396 | type Tab = { 1397 | active: boolean; 1398 | audible?: boolean; 1399 | autoDiscardable?: boolean; 1400 | cookieStoreId?: string; 1401 | discarded?: boolean; 1402 | favIconUrl?: string; 1403 | height?: number; 1404 | hidden: boolean; 1405 | highlighted: boolean; 1406 | id?: number; 1407 | incognito: boolean; 1408 | index: number; 1409 | isArticle: boolean; 1410 | isInReaderMode: boolean; 1411 | lastAccessed: number; 1412 | mutedInfo?: MutedInfo; 1413 | openerTabId?: number; 1414 | pinned: boolean; 1415 | selected: boolean; 1416 | sessionId?: string; 1417 | status?: string; 1418 | title?: string; 1419 | url?: string; 1420 | width?: number; 1421 | windowId: number; 1422 | }; 1423 | 1424 | type TabStatus = "loading" | "complete"; 1425 | type WindowType = "normal" | "popup" | "panel" | "devtools"; 1426 | type ZoomSettingsMode = "automatic" | "disabled" | "manual"; 1427 | type ZoomSettingsScope = "per-origin" | "per-tab"; 1428 | type ZoomSettings = { 1429 | defaultZoomFactor?: number; 1430 | mode?: ZoomSettingsMode; 1431 | scope?: ZoomSettingsScope; 1432 | }; 1433 | 1434 | const TAB_ID_NONE: number; 1435 | 1436 | function connect(tabId: number, connectInfo?: { name?: string; frameId?: number }): browser.runtime.Port; 1437 | function create(createProperties: { 1438 | active?: boolean; 1439 | cookieStoreId?: string; 1440 | index?: number; 1441 | openerTabId?: number; 1442 | pinned?: boolean; 1443 | // deprecated: selected: boolean, 1444 | url?: string; 1445 | windowId?: number; 1446 | }): Promise; 1447 | function captureTab(tabId?: number, options?: browser.extensionTypes.ImageDetails): Promise; 1448 | function captureVisibleTab(windowId?: number, options?: browser.extensionTypes.ImageDetails): Promise; 1449 | function detectLanguage(tabId?: number): Promise; 1450 | function duplicate(tabId: number): Promise; 1451 | function executeScript(tabId: number | undefined, details: browser.extensionTypes.InjectDetails): Promise; 1452 | function get(tabId: number): Promise; 1453 | // deprecated: function getAllInWindow(): x; 1454 | function getCurrent(): Promise; 1455 | // deprecated: function getSelected(windowId?: number): Promise; 1456 | function getZoom(tabId?: number): Promise; 1457 | function getZoomSettings(tabId?: number): Promise; 1458 | function hide(tabIds: number | number[]): Promise; 1459 | // unsupported: function highlight(highlightInfo: { 1460 | // windowId?: number, 1461 | // tabs: number[]|number, 1462 | // }): Promise; 1463 | function insertCSS(tabId: number | undefined, details: browser.extensionTypes.InjectDetailsCSS): Promise; 1464 | function removeCSS(tabId: number | undefined, details: browser.extensionTypes.InjectDetails): Promise; 1465 | function move( 1466 | tabIds: number | number[], 1467 | moveProperties: { 1468 | windowId?: number; 1469 | index: number; 1470 | } 1471 | ): Promise; 1472 | function print(): Promise; 1473 | function printPreview(): Promise; 1474 | function query(queryInfo: { 1475 | active?: boolean; 1476 | audible?: boolean; 1477 | // unsupported: autoDiscardable?: boolean, 1478 | cookieStoreId?: string; 1479 | currentWindow?: boolean; 1480 | discarded?: boolean; 1481 | hidden?: boolean; 1482 | highlighted?: boolean; 1483 | index?: number; 1484 | muted?: boolean; 1485 | lastFocusedWindow?: boolean; 1486 | pinned?: boolean; 1487 | status?: TabStatus; 1488 | title?: string; 1489 | url?: string | string[]; 1490 | windowId?: number; 1491 | windowType?: WindowType; 1492 | }): Promise; 1493 | function reload(tabId?: number, reloadProperties?: { bypassCache?: boolean }): Promise; 1494 | function remove(tabIds: number | number[]): Promise; 1495 | function saveAsPDF( 1496 | pageSettings: PageSettings 1497 | ): Promise<"saved" | "replaced" | "canceled" | "not_saved" | "not_replaced">; 1498 | function sendMessage( 1499 | tabId: number, 1500 | message: T, 1501 | options?: { frameId?: number } 1502 | ): Promise; 1503 | // deprecated: function sendRequest(): x; 1504 | function setZoom(tabId: number | undefined, zoomFactor: number): Promise; 1505 | function setZoomSettings(tabId: number | undefined, zoomSettings: ZoomSettings): Promise; 1506 | function show(tabIds: number | number[]): Promise; 1507 | function toggleReaderMode(tabId?: number): Promise; 1508 | function update( 1509 | tabId: number | undefined, 1510 | updateProperties: { 1511 | active?: boolean; 1512 | // unsupported: autoDiscardable?: boolean, 1513 | // unsupported: highlighted?: boolean, 1514 | // unsupported: hidden?: boolean; 1515 | loadReplace?: boolean; 1516 | muted?: boolean; 1517 | openerTabId?: number; 1518 | pinned?: boolean; 1519 | // deprecated: selected?: boolean, 1520 | url?: string; 1521 | } 1522 | ): Promise; 1523 | 1524 | const onActivated: Listener<{ tabId: number; windowId: number }>; 1525 | const onAttached: EvListener< 1526 | ( 1527 | tabId: number, 1528 | attachInfo: { 1529 | newWindowId: number; 1530 | newPosition: number; 1531 | } 1532 | ) => void 1533 | >; 1534 | const onCreated: Listener; 1535 | const onDetached: EvListener< 1536 | ( 1537 | tabId: number, 1538 | detachInfo: { 1539 | oldWindowId: number; 1540 | oldPosition: number; 1541 | } 1542 | ) => void 1543 | >; 1544 | const onHighlighted: Listener<{ windowId: number; tabIds: number[] }>; 1545 | const onMoved: EvListener< 1546 | ( 1547 | tabId: number, 1548 | moveInfo: { 1549 | windowId: number; 1550 | fromIndex: number; 1551 | toIndex: number; 1552 | } 1553 | ) => void 1554 | >; 1555 | const onRemoved: EvListener< 1556 | ( 1557 | tabId: number, 1558 | removeInfo: { 1559 | windowId: number; 1560 | isWindowClosing: boolean; 1561 | } 1562 | ) => void 1563 | >; 1564 | const onReplaced: EvListener<(addedTabId: number, removedTabId: number) => void>; 1565 | const onUpdated: EvListener< 1566 | ( 1567 | tabId: number, 1568 | changeInfo: { 1569 | audible?: boolean; 1570 | discarded?: boolean; 1571 | favIconUrl?: string; 1572 | mutedInfo?: MutedInfo; 1573 | pinned?: boolean; 1574 | status?: string; 1575 | title?: string; 1576 | url?: string; 1577 | }, 1578 | tab: Tab 1579 | ) => void 1580 | >; 1581 | const onZoomChanged: Listener<{ 1582 | tabId: number; 1583 | oldZoomFactor: number; 1584 | newZoomFactor: number; 1585 | zoomSettings: ZoomSettings; 1586 | }>; 1587 | } 1588 | 1589 | declare namespace browser.topSites { 1590 | type MostVisitedURL = { 1591 | title: string; 1592 | url: string; 1593 | }; 1594 | function get(): Promise; 1595 | } 1596 | 1597 | declare namespace browser.webNavigation { 1598 | type TransitionType = "link" | "auto_subframe" | "form_submit" | "reload"; 1599 | // unsupported: | "typed" | "auto_bookmark" | "manual_subframe" 1600 | // | "generated" | "start_page" | "keyword" 1601 | // | "keyword_generated"; 1602 | 1603 | type TransitionQualifier = "client_redirect" | "server_redirect" | "forward_back"; 1604 | // unsupported: "from_address_bar"; 1605 | 1606 | function getFrame(details: { 1607 | tabId: number; 1608 | processId: number; 1609 | frameId: number; 1610 | }): Promise<{ errorOccured: boolean; url: string; parentFrameId: number }>; 1611 | 1612 | function getAllFrames(details: { tabId: number }): Promise< 1613 | { 1614 | errorOccured: boolean; 1615 | processId: number; 1616 | frameId: number; 1617 | parentFrameId: number; 1618 | url: string; 1619 | }[] 1620 | >; 1621 | 1622 | interface NavListener { 1623 | addListener: ( 1624 | callback: (arg: T) => void, 1625 | filter?: { 1626 | url: browser.events.UrlFilter[]; 1627 | } 1628 | ) => void; 1629 | removeListener: (callback: (arg: T) => void) => void; 1630 | hasListener: (callback: (arg: T) => void) => boolean; 1631 | } 1632 | 1633 | type DefaultNavListener = NavListener<{ 1634 | tabId: number; 1635 | url: string; 1636 | processId: number; 1637 | frameId: number; 1638 | timeStamp: number; 1639 | }>; 1640 | 1641 | type TransitionNavListener = NavListener<{ 1642 | tabId: number; 1643 | url: string; 1644 | processId: number; 1645 | frameId: number; 1646 | timeStamp: number; 1647 | transitionType: TransitionType; 1648 | transitionQualifiers: TransitionQualifier[]; 1649 | }>; 1650 | 1651 | const onBeforeNavigate: NavListener<{ 1652 | tabId: number; 1653 | url: string; 1654 | processId: number; 1655 | frameId: number; 1656 | parentFrameId: number; 1657 | timeStamp: number; 1658 | }>; 1659 | 1660 | const onCommitted: TransitionNavListener; 1661 | 1662 | const onCreatedNavigationTarget: NavListener<{ 1663 | sourceFrameId: number; 1664 | // Unsupported: sourceProcessId: number, 1665 | sourceTabId: number; 1666 | tabId: number; 1667 | timeStamp: number; 1668 | url: string; 1669 | windowId: number; 1670 | }>; 1671 | 1672 | const onDOMContentLoaded: DefaultNavListener; 1673 | 1674 | const onCompleted: DefaultNavListener; 1675 | 1676 | const onErrorOccurred: DefaultNavListener; // error field unsupported 1677 | 1678 | const onReferenceFragmentUpdated: TransitionNavListener; 1679 | 1680 | const onHistoryStateUpdated: TransitionNavListener; 1681 | } 1682 | 1683 | declare namespace browser.webRequest { 1684 | type ResourceType = 1685 | | "main_frame" 1686 | | "sub_frame" 1687 | | "stylesheet" 1688 | | "script" 1689 | | "image" 1690 | | "object" 1691 | | "xmlhttprequest" 1692 | | "xbl" 1693 | | "xslt" 1694 | | "ping" 1695 | | "beacon" 1696 | | "xml_dtd" 1697 | | "font" 1698 | | "media" 1699 | | "websocket" 1700 | | "csp_report" 1701 | | "imageset" 1702 | | "web_manifest" 1703 | | "other"; 1704 | 1705 | type RequestFilter = { 1706 | urls: string[]; 1707 | types?: ResourceType[]; 1708 | tabId?: number; 1709 | windowId?: number; 1710 | }; 1711 | 1712 | type StreamFilter = { 1713 | onstart: (event: any) => void; 1714 | ondata: (event: { data: ArrayBuffer }) => void; 1715 | onstop: (event: any) => void; 1716 | onerror: (event: any) => void; 1717 | 1718 | close(): void; 1719 | disconnect(): void; 1720 | resume(): void; 1721 | suspend(): void; 1722 | write(data: Uint8Array | ArrayBuffer): void; 1723 | 1724 | error: string; 1725 | status: 1726 | | "uninitialized" 1727 | | "transferringdata" 1728 | | "finishedtransferringdata" 1729 | | "suspended" 1730 | | "closed" 1731 | | "disconnected" 1732 | | "failed"; 1733 | }; 1734 | 1735 | type HttpHeaders = ( 1736 | | { name: string; binaryValue: number[]; value?: string } 1737 | | { name: string; value: string; binaryValue?: number[] } 1738 | )[]; 1739 | 1740 | type BlockingResponse = { 1741 | cancel?: boolean; 1742 | redirectUrl?: string; 1743 | requestHeaders?: HttpHeaders; 1744 | responseHeaders?: HttpHeaders; 1745 | authCredentials?: { username: string; password: string }; 1746 | }; 1747 | 1748 | type UploadData = { 1749 | bytes?: ArrayBuffer; 1750 | file?: string; 1751 | }; 1752 | 1753 | const MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES: number; 1754 | 1755 | function handlerBehaviorChanged(): Promise; 1756 | 1757 | // TODO: Enforce the return result of the addListener call in the contract 1758 | // Use an intersection type for all the default properties 1759 | interface ReqListener { 1760 | addListener: ( 1761 | callback: (arg: T) => void, 1762 | filter: RequestFilter, 1763 | extraInfoSpec?: Array 1764 | ) => BlockingResponse | Promise; 1765 | removeListener: (callback: (arg: T) => void) => void; 1766 | hasListener: (callback: (arg: T) => void) => boolean; 1767 | } 1768 | 1769 | const onBeforeRequest: ReqListener< 1770 | { 1771 | requestId: string; 1772 | url: string; 1773 | method: string; 1774 | frameId: number; 1775 | parentFrameId: number; 1776 | requestBody?: { 1777 | error?: string; 1778 | formData?: { [key: string]: string[] }; 1779 | raw?: UploadData[]; 1780 | }; 1781 | tabId: number; 1782 | type: ResourceType; 1783 | timeStamp: number; 1784 | originUrl: string; 1785 | }, 1786 | "blocking" | "requestBody" 1787 | >; 1788 | 1789 | const onBeforeSendHeaders: ReqListener< 1790 | { 1791 | requestId: string; 1792 | url: string; 1793 | method: string; 1794 | frameId: number; 1795 | parentFrameId: number; 1796 | tabId: number; 1797 | type: ResourceType; 1798 | timeStamp: number; 1799 | originUrl: string; 1800 | requestHeaders?: HttpHeaders; 1801 | }, 1802 | "blocking" | "requestHeaders" 1803 | >; 1804 | 1805 | const onSendHeaders: ReqListener< 1806 | { 1807 | requestId: string; 1808 | url: string; 1809 | method: string; 1810 | frameId: number; 1811 | parentFrameId: number; 1812 | tabId: number; 1813 | type: ResourceType; 1814 | timeStamp: number; 1815 | originUrl: string; 1816 | requestHeaders?: HttpHeaders; 1817 | }, 1818 | "requestHeaders" 1819 | >; 1820 | 1821 | const onHeadersReceived: ReqListener< 1822 | { 1823 | requestId: string; 1824 | url: string; 1825 | method: string; 1826 | frameId: number; 1827 | parentFrameId: number; 1828 | tabId: number; 1829 | type: ResourceType; 1830 | timeStamp: number; 1831 | originUrl: string; 1832 | statusLine: string; 1833 | responseHeaders?: HttpHeaders; 1834 | statusCode: number; 1835 | }, 1836 | "blocking" | "responseHeaders" 1837 | >; 1838 | 1839 | const onAuthRequired: ReqListener< 1840 | { 1841 | requestId: string; 1842 | url: string; 1843 | method: string; 1844 | frameId: number; 1845 | parentFrameId: number; 1846 | tabId: number; 1847 | type: ResourceType; 1848 | timeStamp: number; 1849 | scheme: string; 1850 | realm?: string; 1851 | challenger: { host: string; port: number }; 1852 | isProxy: boolean; 1853 | responseHeaders?: HttpHeaders; 1854 | statusLine: string; 1855 | statusCode: number; 1856 | }, 1857 | "blocking" | "responseHeaders" 1858 | >; 1859 | 1860 | const onResponseStarted: ReqListener< 1861 | { 1862 | requestId: string; 1863 | url: string; 1864 | method: string; 1865 | frameId: number; 1866 | parentFrameId: number; 1867 | tabId: number; 1868 | type: ResourceType; 1869 | timeStamp: number; 1870 | originUrl: string; 1871 | ip?: string; 1872 | fromCache: boolean; 1873 | statusLine: string; 1874 | responseHeaders?: HttpHeaders; 1875 | statusCode: number; 1876 | }, 1877 | "responseHeaders" 1878 | >; 1879 | 1880 | const onBeforeRedirect: ReqListener< 1881 | { 1882 | requestId: string; 1883 | url: string; 1884 | method: string; 1885 | frameId: number; 1886 | parentFrameId: number; 1887 | tabId: number; 1888 | type: ResourceType; 1889 | timeStamp: number; 1890 | originUrl: string; 1891 | ip?: string; 1892 | fromCache: boolean; 1893 | statusCode: number; 1894 | redirectUrl: string; 1895 | statusLine: string; 1896 | responseHeaders?: HttpHeaders; 1897 | }, 1898 | "responseHeaders" 1899 | >; 1900 | 1901 | const onCompleted: ReqListener< 1902 | { 1903 | requestId: string; 1904 | url: string; 1905 | method: string; 1906 | frameId: number; 1907 | parentFrameId: number; 1908 | tabId: number; 1909 | type: ResourceType; 1910 | timeStamp: number; 1911 | originUrl: string; 1912 | ip?: string; 1913 | fromCache: boolean; 1914 | statusCode: number; 1915 | statusLine: string; 1916 | responseHeaders?: HttpHeaders; 1917 | }, 1918 | "responseHeaders" 1919 | >; 1920 | 1921 | const onErrorOccurred: ReqListener< 1922 | { 1923 | requestId: string; 1924 | url: string; 1925 | method: string; 1926 | frameId: number; 1927 | parentFrameId: number; 1928 | tabId: number; 1929 | type: ResourceType; 1930 | timeStamp: number; 1931 | originUrl: string; 1932 | ip?: string; 1933 | fromCache: boolean; 1934 | error: string; 1935 | }, 1936 | void 1937 | >; 1938 | 1939 | function filterResponseData(requestId: string): StreamFilter; 1940 | } 1941 | 1942 | declare namespace browser.windows { 1943 | type WindowType = "normal" | "popup" | "panel" | "devtools"; 1944 | 1945 | type WindowState = "normal" | "minimized" | "maximized" | "fullscreen" | "docked"; 1946 | 1947 | type Window = { 1948 | id?: number; 1949 | focused: boolean; 1950 | top?: number; 1951 | left?: number; 1952 | width?: number; 1953 | height?: number; 1954 | tabs?: browser.tabs.Tab[]; 1955 | incognito: boolean; 1956 | type?: WindowType; 1957 | state?: WindowState; 1958 | alwaysOnTop: boolean; 1959 | sessionId?: string; 1960 | }; 1961 | 1962 | type CreateType = "normal" | "popup" | "panel" | "detached_panel"; 1963 | 1964 | const WINDOW_ID_NONE: number; 1965 | 1966 | const WINDOW_ID_CURRENT: number; 1967 | 1968 | function get( 1969 | windowId: number, 1970 | getInfo?: { 1971 | populate?: boolean; 1972 | windowTypes?: WindowType[]; 1973 | } 1974 | ): Promise; 1975 | 1976 | function getCurrent(getInfo?: { populate?: boolean; windowTypes?: WindowType[] }): Promise; 1977 | 1978 | function getLastFocused(getInfo?: { 1979 | populate?: boolean; 1980 | windowTypes?: WindowType[]; 1981 | }): Promise; 1982 | 1983 | function getAll(getInfo?: { populate?: boolean; windowTypes?: WindowType[] }): Promise; 1984 | 1985 | // TODO: url and tabId should be exclusive 1986 | function create(createData?: { 1987 | allowScriptsToClose?: boolean; 1988 | url?: string | string[]; 1989 | tabId?: number; 1990 | left?: number; 1991 | top?: number; 1992 | width?: number; 1993 | height?: number; 1994 | // unsupported: focused?: boolean, 1995 | incognito?: boolean; 1996 | titlePreface?: string; 1997 | type?: CreateType; 1998 | state?: WindowState; 1999 | }): Promise; 2000 | 2001 | function update( 2002 | windowId: number, 2003 | updateInfo: { 2004 | left?: number; 2005 | top?: number; 2006 | width?: number; 2007 | height?: number; 2008 | focused?: boolean; 2009 | drawAttention?: boolean; 2010 | state?: WindowState; 2011 | } 2012 | ): Promise; 2013 | 2014 | function remove(windowId: number): Promise; 2015 | 2016 | const onCreated: Listener; 2017 | 2018 | const onRemoved: Listener; 2019 | 2020 | const onFocusChanged: Listener; 2021 | } 2022 | 2023 | declare namespace browser.theme { 2024 | type Theme = { 2025 | images: ThemeImages; 2026 | colors: ThemeColors; 2027 | properties?: ThemeProperties; 2028 | }; 2029 | 2030 | type ThemeImages = { 2031 | headerURL: string; 2032 | theme_frame?: string; 2033 | additional_backgrounds?: string[]; 2034 | }; 2035 | 2036 | type ThemeColors = { 2037 | accentcolor: string; 2038 | textcolor: string; 2039 | frame?: [number, number, number]; 2040 | tab_text?: [number, number, number]; 2041 | toolbar?: string; 2042 | toolbar_text?: string; 2043 | toolbar_field?: string; 2044 | toolbar_field_text?: string; 2045 | }; 2046 | 2047 | type ThemeProperties = { 2048 | additional_backgrounds_alignment: Alignment[]; 2049 | additional_backgrounds_tiling: Tiling[]; 2050 | }; 2051 | 2052 | type Alignment = 2053 | | "bottom" 2054 | | "center" 2055 | | "left" 2056 | | "right" 2057 | | "top" 2058 | | "center bottom" 2059 | | "center center" 2060 | | "center top" 2061 | | "left bottom" 2062 | | "left center" 2063 | | "left top" 2064 | | "right bottom" 2065 | | "right center" 2066 | | "right top"; 2067 | 2068 | type Tiling = "no-repeat" | "repeat" | "repeat-x" | "repeat-y"; 2069 | 2070 | function getCurrent(): Promise; 2071 | function getCurrent(windowId: number): Promise; 2072 | function update(theme: Theme): Promise; 2073 | function update(windowId: number, theme: Theme): Promise; 2074 | function reset(): Promise; 2075 | function reset(windowId: number): Promise; 2076 | } 2077 | --------------------------------------------------------------------------------