├── .babelrc
├── .chglog
├── CHANGELOG.tpl.md
└── config.yml
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── android
│ ├── update_manifest.py
│ └── updates.json
│ └── build_legacy.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── scripts
└── build-zip.js
├── src
├── _locales
│ ├── de
│ │ └── messages.json
│ ├── en
│ │ └── messages.json
│ ├── es
│ │ └── messages.json
│ ├── fi
│ │ └── messages.json
│ ├── fr
│ │ └── messages.json
│ ├── index.ts
│ ├── it
│ │ └── messages.json
│ ├── ja
│ │ └── messages.json
│ ├── ko
│ │ └── messages.json
│ ├── pl
│ │ └── messages.json
│ ├── pt_BR
│ │ └── messages.json
│ ├── pt_PT
│ │ └── messages.json
│ ├── ru
│ │ └── messages.json
│ ├── tr
│ │ └── messages.json
│ └── zh_CN
│ │ └── messages.json
├── background.ts
├── browser.d.ts
├── css
│ ├── notosans.ttf
│ └── tailwind.css
├── icons
│ ├── icon.svg
│ └── icon_disabled.svg
├── lib
│ ├── chameleon.ts
│ ├── devices.ts
│ ├── inject.ts
│ ├── intercept.ts
│ ├── language.ts
│ ├── profiles.ts
│ ├── spoof
│ │ ├── audioContext.ts
│ │ ├── clientRects.ts
│ │ ├── cssExfil.ts
│ │ ├── font.ts
│ │ ├── history.ts
│ │ ├── kbFingerprint.ts
│ │ ├── language.ts
│ │ ├── media.ts
│ │ ├── mediaSpoof.ts
│ │ ├── name.ts
│ │ ├── navigator.ts
│ │ ├── quirks.ts
│ │ ├── referer.ts
│ │ ├── screen.ts
│ │ └── timezone.ts
│ ├── tz.ts
│ ├── util.ts
│ ├── webext.ts
│ └── whitelisted.ts
├── manifest.json
├── options
│ ├── App.vue
│ ├── options.html
│ └── options.ts
├── popup
│ ├── App.vue
│ ├── popup.html
│ └── popup.ts
└── store
│ ├── actions.ts
│ ├── index.ts
│ ├── mutation-types.ts
│ └── mutations.ts
├── tailwind.config.js
├── tests
├── headers.spec.js
├── options.spec.js
├── server
│ ├── index.ejs
│ └── sha1.js
├── setup.js
└── teardown.js
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@babel/plugin-proposal-optional-chaining",
4 | "@babel/plugin-proposal-export-default-from"
5 | ],
6 | "presets": [
7 | ["@babel/preset-env", {
8 | "useBuiltIns": "usage",
9 | "corejs": 3,
10 | "targets": {
11 | "firefox": "68"
12 | }
13 | }]
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | {{ range .Versions }}
2 |
3 |
4 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
5 |
6 | {{ range .CommitGroups -}}
7 |
8 | ### {{ .Title }}
9 |
10 | {{ range .Commits -}}
11 |
12 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
13 | {{ end }}
14 | {{ end -}}
15 |
16 | {{- if .MergeCommits -}}
17 |
18 | ### Pull Requests
19 |
20 | {{ range .MergeCommits -}}
21 |
22 | - {{ .Header }}
23 | {{ end }}
24 | {{ end -}}
25 |
26 | {{- if .NoteGroups -}}
27 | {{ range .NoteGroups -}}
28 |
29 | ### {{ .Title }}
30 |
31 | {{ range .Notes }}
32 | {{ .Body }}
33 | {{ end }}
34 | {{ end -}}
35 | {{ end -}}
36 | {{ end -}}
37 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/sereneblue/chameleon
6 | options:
7 | commits:
8 | filters:
9 | Type:
10 | - feat
11 | - fix
12 | sort_by: Scope
13 | commit_groups:
14 | title_maps:
15 | feat: Features
16 | fix: Bug Fixes
17 | # perf: Performance Improvements
18 | # refactor: Code Refactoring
19 | header:
20 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
21 | pattern_maps:
22 | - Type
23 | - Scope
24 | - Subject
25 | notes:
26 | keywords:
27 | - BREAKING CHANGE
28 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 | // File taken from https://github.com/vuejs-templates/webpack/blob/1.3.1/template/.eslintrc.js, thanks.
3 |
4 | module.exports = {
5 | root: true,
6 | parserOptions: {
7 | parser: 'babel-eslint',
8 | },
9 | env: {
10 | browser: true,
11 | webextensions: true,
12 | },
13 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
14 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
15 | extends: ['plugin:vue/essential', 'airbnb-base', 'plugin:prettier/recommended'],
16 | // required to lint *.vue files
17 | plugins: ['vue'],
18 | // check if imports actually resolve
19 | settings: {
20 | 'import/resolver': {
21 | webpack: {
22 | config: './webpack.config.js',
23 | },
24 | },
25 | },
26 | // add your custom rules here
27 | rules: {
28 | // don't require .vue extension when importing
29 | 'import/extensions': [
30 | 'error',
31 | 'always',
32 | {
33 | js: 'never',
34 | vue: 'never',
35 | },
36 | ],
37 | // disallow reassignment of function parameters
38 | // disallow parameter object manipulation except for specific exclusions
39 | 'no-param-reassign': [
40 | 'error',
41 | {
42 | props: true,
43 | ignorePropertyModificationsFor: [
44 | 'state', // for vuex state
45 | 'acc', // for reduce accumulators
46 | 'e', // for e.returnvalue
47 | ],
48 | },
49 | ],
50 | // disallow default export over named export
51 | 'import/prefer-default-export': 'off',
52 | // allow debugger during development
53 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
54 | },
55 | };
56 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | src/manifest.json merge=ours
2 | README.md merge=ours
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 |
3 | Please use issues for bugs only! Answer the following questions for yourself before submitting an issue: **YOU MAY DELETE THE PREREQUISITES SECTION.**
4 |
5 | - [ ] I am running the latest version
6 | - [ ] I checked the documentation and found no answer
7 | - [ ] I checked to make sure that this issue has not already been filed
8 |
9 | ## Expected Behavior
10 |
11 | ## Current Behavior
12 |
13 | ## Relevant settings
14 |
15 |
16 |
17 | ## Context (Environment)
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/android/update_manifest.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | manifest = {}
4 |
5 | with open('./src/manifest.json') as f:
6 | manifest = json.load(f)
7 |
8 | # increment version for android build
9 | version = manifest['version'].split('.')
10 | version[-1] = str(int(version[-1]) + 1)
11 |
12 | manifest['version'] = ".".join(version)
13 | manifest['version_name'] = ".".join(version[:-1])
14 |
15 | # add update url
16 | manifest['browser_specific_settings']['gecko']['update_url'] = 'https://raw.githubusercontent.com/sereneblue/chameleon/master/.github/workflows/android/updates.json'
17 |
18 | # remove optional permissions
19 | del manifest['optional_permissions']
20 |
21 | manifest['permissions'].append('privacy')
22 |
23 | with open('./src/manifest.json', 'w') as f:
24 | json.dump(manifest, f, indent=2)
--------------------------------------------------------------------------------
/.github/workflows/android/updates.json:
--------------------------------------------------------------------------------
1 | {
2 | "addons": {
3 | "{3579f63b-d8ee-424f-bbb6-6d0ce3285e6a}": {
4 | "updates": [
5 | {
6 | "version": "0.22.73.2",
7 | "browser_specific_settings": {
8 | "gecko": { "strict_min_version": "68.0a1" },
9 | "gecko_android": { "strict_min_version": "68.0a1" }
10 | },
11 | "update_link": "https://github.com/sereneblue/chameleon/releases/download/v0.22.73/chameleon_v0.22.73.signed.xpi"
12 | }
13 | ]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/workflows/build_legacy.yml:
--------------------------------------------------------------------------------
1 | name: Chameleon Legacy Build
2 |
3 | on:
4 | push:
5 | tags:
6 | - v**
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Check out repo
13 | uses: actions/checkout@v4
14 | - name: Install NodeJS
15 | uses: actions/setup-node@v4
16 | with:
17 | node-version: '18'
18 | - name: Build extension
19 | run: |
20 | npm install
21 | npm install --global web-ext
22 | python3 ./.github/workflows/android/update_manifest.py
23 | npm run build
24 | - name: Sign extension
25 | run: |
26 | web-ext sign -s ./dist --channel unlisted
27 | mv ./web-ext-artifacts/$(ls web-ext-artifacts) ./web-ext-artifacts/chameleon_$(git describe --tags).signed.xpi
28 | env:
29 | WEB_EXT_API_KEY: ${{ secrets.WEBEXT_API_KEY }}
30 | WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_API_SECRET }}
31 | - name: Generate variables
32 | id: chameleon
33 | run: |
34 | echo "VERSION=$(git describe --tags)" >> $GITHUB_OUTPUT
35 | echo "FILENAME=chameleon_$(git describe --tags).signed.xpi" >> $GITHUB_OUTPUT
36 | - name: Create release
37 | id: create_release
38 | uses: softprops/action-gh-release@v2
39 | with:
40 | token: ${{ secrets.GITHUB_TOKEN }}
41 | tag_name: ${{ steps.chameleon.outputs.VERSION }}
42 | name: Chameleon Legacy Build ${{ steps.chameleon.outputs.VERSION }}
43 | body: 'This version of Chameleon is for users using an older version Firefox 75 and would like to have Chameleon control their privacy settings. More info can be found [here](https://sereneblue.github.io/chameleon/wiki/legacy).'
44 | draft: false
45 | prerelease: false
46 | files: ./web-ext-artifacts/${{ steps.chameleon.outputs.FILENAME }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /*.log
3 | /dist
4 | /dist-zip
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 180,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chameleon
2 |
3 | 
4 | 
5 | [](https://crowdin.com/project/chameleon)
6 |
7 | Chameleon is a WebExtension port of the popular Firefox addon [Random Agent Spoofer](https://github.com/dillbyrne/random-agent-spoofer).
8 |
9 | The UI is near identical and contains most of the features found in the original extension.
10 |
11 | ## Features
12 |
13 | ### UI
14 |
15 | - Light/Dark theme
16 | - Notifications
17 | - Quickly toggle Chameleon
18 |
19 | ### Useragents
20 |
21 | - Randomly select from a list of browser profiles
22 | - Choose between different platforms or device types
23 | - Change user agent at specified interval
24 |
25 | ### Headers
26 |
27 | - Enable Do Not Track
28 | - Prevent Etag tracking
29 | - Spoof accept headers
30 | - Spoof X-Forwarded-For/Via IP
31 | - Disable referer
32 | - Modify referer policies
33 |
34 | ### Options
35 |
36 | - Block media devices
37 | - Limit tab history
38 | - Protect keyboard fingerprint
39 | - Protect window.name
40 | - Spoof audio context
41 | - Spoof client rects
42 | - Spoof font fingerprint
43 | - Spoof screen size
44 | - Spoof timezone
45 | - Enable first party isolation
46 | - Enable resist fingerprinting
47 | - Prevent WebRTC leak.
48 | - Enable tracking protection
49 | - Block WebSockets
50 | - Modify cookie policy
51 |
52 | Please note that WebExtensions are unable to modify about:config entries.
53 |
54 | ### Whitelist
55 |
56 | - Use your real or spoofed profile for whitelisted sites
57 | - Supports regular expressions
58 | - Use a custom profile per whitelist rule, multiple sites per rule
59 |
60 | ## Installation
61 |
62 | Chameleon is available on the [Firefox Add-ons website](https://addons.mozilla.org/firefox/addon/chameleon-ext). A developer build is also available on [Github](https://github.com/sereneblue/chameleon/releases).
63 |
64 | ## Contribute
65 |
66 | Want to help improve Chameleon? Send a pull request or open an issue. Keep in mind that some functionality isn't technically possible.
67 |
68 | You can help translate Chameleon by visiting [Crowdin](https://crowdin.com/project/chameleon).
69 |
70 | ## Wiki
71 |
72 | Don't know where to start? Check out the [wiki](https://sereneblue.github.io/chameleon/wiki). If you're having issues with a website, please read the whitelist [guide](https://sereneblue.github.io/chameleon/wiki/whitelist).
73 |
74 | ## Credits
75 |
76 | [dillbyrne](https://github.com/dillbyrne) for creating [Random Agent Spoofer](https://github.com/dillbyrne/random-agent-spoofer)
77 |
78 | [Mike Gualtieri](https://github.com/mlgualtieri) for the [CSS Exfil](https://github.com/mlgualtieri/CSS-Exfil-Protection) code.
79 |
80 | ## Special Thanks
81 |
82 | - giwhub for the Chinese translation
83 | - Kaylier, melegeti, and Tux 528 for the French translation
84 | - Anonymous, Wursttorte, AName, and robinmusch for the German translation
85 | - Shitennouji for the Japanese translation
86 | - gnu-ewm for the Polish translation
87 | - 3ibsand, Alexey, fks7cgsk, Hit Legends, EugeneKh and Дмитрий Кондрашов for the Russian translation
88 | - David P. (Megver83), reii and gallegonovato for the Spanish translation
89 | - xlabx for the Turkish translation
90 | - mezysinc and Lucas Guima for the Portuguese (Brazilian) translation
91 | - Ricky Tigg for the Finnish translation
92 | - LolloGamer_5123 YT for the Italian translation
93 | - azilara for the Portuguese translation
94 | - maiska for the Korean translation
95 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chameleon-ext",
3 | "version": "0.22.73",
4 | "description": "Spoof your browser profile. Includes a few privacy enhancing options.",
5 | "author": "sereneblue",
6 | "license": "GPLv3",
7 | "scripts": {
8 | "lint": "eslint --ext .js,.vue src",
9 | "prettier": "prettier \"src/**/*.{js,vue}\"",
10 | "prettier:write": "npm run prettier -- --write",
11 | "build": "cross-env NODE_ENV=production NODE_OPTIONS=--openssl-legacy-provider webpack --hide-modules",
12 | "build:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--openssl-legacy-provider webpack --hide-modules",
13 | "build-zip": "node scripts/build-zip.js",
14 | "test": "npm run build && npm run build-zip && jest --detectOpenHandles",
15 | "watch": "npm run build -- --watch",
16 | "watch:dev": "cross-env HMR=true NODE_OPTIONS=--openssl-legacy-provider npm run build:dev -- --watch"
17 | },
18 | "husky": {
19 | "hooks": {
20 | "pre-commit": "pretty-quick --staged"
21 | }
22 | },
23 | "jest": {
24 | "globals": {
25 | "__DEV__": true
26 | },
27 | "globalSetup": "/tests/setup.js",
28 | "globalTeardown": "/tests/teardown.js",
29 | "transform": {
30 | "^.+\\.(ts|tsx)$": "ts-jest"
31 | }
32 | },
33 | "dependencies": {
34 | "cidr-js": "^2.3.1",
35 | "feather-icons": "^4.29.1",
36 | "moment-timezone": "^0.5.45",
37 | "psl": "^1.8.0",
38 | "tailwindcss": "^1.9.6",
39 | "uuid": "^8.3.2",
40 | "vue": "^2.6.14",
41 | "vue-class-component": "^7.2.6",
42 | "vue-feather": "^1.1.1",
43 | "vue-i18n": "^8.27.2",
44 | "vue-object-merge": "^0.1.8",
45 | "vue-property-decorator": "^8.5.1",
46 | "vue2-perfect-scrollbar": "^1.5.2",
47 | "vuex": "^3.6.2"
48 | },
49 | "devDependencies": {
50 | "@babel/core": "^7.12.10",
51 | "@babel/plugin-proposal-export-default-from": "^7.12.1",
52 | "@babel/plugin-proposal-optional-chaining": "^7.12.7",
53 | "@babel/preset-env": "^7.24.3",
54 | "@babel/runtime-corejs3": "^7.12.5",
55 | "@fullhuman/postcss-purgecss": "^2.3.0",
56 | "@tailwindcss/custom-forms": "^0.2.1",
57 | "@types/chrome": "^0.0.74",
58 | "@types/node": "^12.19.15",
59 | "archiver": "^3.0.0",
60 | "babel-eslint": "^10.0.1",
61 | "babel-loader": "^8.2.2",
62 | "copy-webpack-plugin": "^5.1.2",
63 | "core-js": "^3.8.3",
64 | "cross-env": "^7.0.3",
65 | "css-loader": "^2.1.1",
66 | "ejs": "^3.1.5",
67 | "eslint": "^5.16.0",
68 | "eslint-config-airbnb-base": "^13.0.0",
69 | "eslint-config-prettier": "^4.3.0",
70 | "eslint-friendly-formatter": "^4.0.1",
71 | "eslint-import-resolver-webpack": "^0.10.1",
72 | "eslint-loader": "^2.1.2",
73 | "eslint-plugin-import": "^2.22.1",
74 | "eslint-plugin-prettier": "^3.3.1",
75 | "eslint-plugin-vue": "^5.2.2",
76 | "express": "^4.17.1",
77 | "express-ws": "^4.0.0",
78 | "file-loader": "^1.1.11",
79 | "geckodriver": "^1.22.1",
80 | "husky": "^2.7.0",
81 | "jest": "^26.6.3",
82 | "mini-css-extract-plugin": "^0.4.4",
83 | "postcss-loader": "^3.0.0",
84 | "prettier": "^1.19.1",
85 | "pretty-quick": "^1.8.0",
86 | "selenium-webdriver": "^4.0.0-alpha.8",
87 | "ts-jest": "^26.5.0",
88 | "ts-loader": "^8.0.0",
89 | "typescript": "^4.1.3",
90 | "vue-loader": "^15.9.6",
91 | "vue-template-compiler": "^2.6.12",
92 | "web-ext": "^7.11.0",
93 | "webpack": "^4.47.0",
94 | "webpack-cli": "^3.3.12",
95 | "webpack-extension-reloader": "^1.1.4"
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const purgecss = require('@fullhuman/postcss-purgecss')({
2 | // Specify the paths to all of the template files in your project
3 | content: ['./src/**/*.html', './src/**/*.vue', './src/**/*.jsx'],
4 |
5 | // Include any special characters you're using in this regular expression
6 | defaultExtractor: content => content.match(/[\w-/:]+(? {
11 | const extPackageJson = require('../package.json');
12 |
13 | return {
14 | name: extPackageJson.name,
15 | version: extPackageJson.version,
16 | };
17 | };
18 |
19 | const makeDestZipDirIfNotExists = () => {
20 | if (!fs.existsSync(DEST_ZIP_DIR)) {
21 | fs.mkdirSync(DEST_ZIP_DIR);
22 | }
23 | };
24 |
25 | const buildZip = (src, dist, zipFilename) => {
26 | console.info(`Building ${zipFilename}...`);
27 |
28 | const archive_zip = archiver('zip', { zlib: { level: 9 } });
29 | const archive_xpi = archiver('zip', { zlib: { level: 9 } });
30 |
31 | const stream = fs.createWriteStream(path.join(dist, zipFilename));
32 | const xpiStream = fs.createWriteStream(path.join(dist, zipFilename.replace('.zip', '.xpi')));
33 |
34 | return new Promise((resolve, reject) => {
35 | stream.on('close', () => resolve());
36 | xpiStream.on('close', () => resolve());
37 |
38 | archive_zip
39 | .directory(src, false)
40 | .on('error', err => reject(err))
41 | .pipe(stream);
42 | archive_zip.finalize();
43 |
44 | archive_xpi
45 | .directory(src, false)
46 | .on('error', err => reject(err))
47 | .pipe(xpiStream);
48 | archive_xpi.finalize();
49 | });
50 | };
51 |
52 | const main = () => {
53 | const { name, version } = extractExtensionData();
54 | const zipFilename = `${name}-v${version}.zip`;
55 |
56 | makeDestZipDirIfNotExists();
57 |
58 | buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
59 | .then(() => console.info('OK'))
60 | .catch(console.err);
61 | };
62 |
63 | main();
64 |
--------------------------------------------------------------------------------
/src/_locales/index.ts:
--------------------------------------------------------------------------------
1 | const en = require('./en/messages.json');
2 | const es = require('./es/messages.json');
3 | const de = require('./de/messages.json');
4 | const fi = require('./fi/messages.json');
5 | const fr = require('./fr/messages.json');
6 | const it = require('./it/messages.json');
7 | const ja = require('./ja/messages.json');
8 | const ko = require('./ko/messages.json');
9 | const pl = require('./pl/messages.json');
10 | const pt_PT = require('./pt_PT/messages.json');
11 | const pt_BR = require('./pt_BR/messages.json');
12 | const ru = require('./ru/messages.json');
13 | const tr = require('./tr/messages.json');
14 | const zh_CN = require('./zh_CN/messages.json');
15 |
16 | export default {
17 | en,
18 | es,
19 | de,
20 | fi,
21 | fr,
22 | it,
23 | ja,
24 | ko,
25 | pl,
26 | pt_PT,
27 | pt_BR,
28 | ru,
29 | tr,
30 | zh_CN,
31 | };
32 |
--------------------------------------------------------------------------------
/src/_locales/ja/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "ブラウザのプロファイルを偽装します。いくつかのプライバシー強化オプションを含みます。"
4 | },
5 | "notifications-profileChange": {
6 | "message": "プロフィ―ルが変更されました。"
7 | },
8 | "notifications-unableToGetIPInfo": {
9 | "message": "IP 情報を取得できません"
10 | },
11 | "notifications-usingIPInfo": {
12 | "message": "IP 情報を使用して:"
13 | },
14 | "notifications-usingIPRule": {
15 | "message": "IP ルールを使用して:"
16 | },
17 | "options-about-issueTracker": {
18 | "message": "Issue Tracker(課題管理)"
19 | },
20 | "options-about-knownIssues": {
21 | "message": "既知の課題"
22 | },
23 | "options-about-wiki": {
24 | "message": "Wiki"
25 | },
26 | "options-about-support": {
27 | "message": "サポート"
28 | },
29 | "options-about-sourceCode": {
30 | "message": "ソースコード"
31 | },
32 | "options-about-translate": {
33 | "message": "翻訳を手伝う"
34 | },
35 | "options-import-couldNotImport": {
36 | "message": "ファイルをインポートできませんでした。"
37 | },
38 | "options-import-invalid-config": {
39 | "message": "不正な環境設定:設定構成の欠落"
40 | },
41 | "options-import-invalid-excluded": {
42 | "message": "不正な環境設定:除外されたものが欠落"
43 | },
44 | "options-import-invalid-excludedProfile": {
45 | "message": "不正な環境設定:除外されたプロファイルが欠落"
46 | },
47 | "options-import-invalid-headers": {
48 | "message": "不正な環境設定:ヘッダの欠落"
49 | },
50 | "options-import-invalid-ipRuleId": {
51 | "message": "不正な環境設定:無効な IP ルールの ID"
52 | },
53 | "options-import-invalid-ipRuleName": {
54 | "message": "不正な環境設定:IP ルール名が欠落"
55 | },
56 | "options-import-invalid-ipRuleRange": {
57 | "message": "不正な環境設定:無効な IP ルールの範囲"
58 | },
59 | "options-import-invalid-ipRules": {
60 | "message": "不正な環境設定:IP ルールの欠落"
61 | },
62 | "options-import-invalid-ipRulesDupe": {
63 | "message": "不正な設定:IP ルール ID の重複が見つかりました"
64 | },
65 | "options-import-invalid-options": {
66 | "message": "不正な設定:オプションの欠落"
67 | },
68 | "options-import-invalid-profile": {
69 | "message": "不正な設定:プロファイルの欠落"
70 | },
71 | "options-import-invalid-setting": {
72 | "message": "不正な設定値:"
73 | },
74 | "options-import-invalid-settings": {
75 | "message": "不正な環境設定:環境設定が欠落"
76 | },
77 | "options-import-invalid-spoofIP": {
78 | "message": "不正な設定:偽装したヘッダの IP 範囲に誤りがあります"
79 | },
80 | "options-import-invalid-version": {
81 | "message": "不正な設定:バージョンが受け付けられません"
82 | },
83 | "options-import-invalid-whitelist": {
84 | "message": "不正な設定:ホワイトリストの欠落"
85 | },
86 | "options-import-invalid-whitelistDupe": {
87 | "message": "不正な設定:重複するホワイトリストのルール ID が見つかりました"
88 | },
89 | "options-import-invalid-whitelistId": {
90 | "message": "不正な設定:無効なホワイトリストのルール ID"
91 | },
92 | "options-import-invalid-whitelistName": {
93 | "message": "不正な設定:ホワイトリストルール名の欠落"
94 | },
95 | "options-import-invalid-whitelistOpt": {
96 | "message": "不正な設定:ホワイトリストルールのオプションが不正です"
97 | },
98 | "options-import-invalid-whitelistSpoofIP": {
99 | "message": "不正な設定:ホワイトリストルールの偽装 IP が不正です"
100 | },
101 | "options-import-success": {
102 | "message": "環境設定を正常にインポートしました。拡張機能の再読み込み中..."
103 | },
104 | "options-ipRules-editorTitle": {
105 | "message": "IP ルールエディタ"
106 | },
107 | "options-ipRules-ipRule": {
108 | "message": "IP ルール"
109 | },
110 | "options-ipRules-reload": {
111 | "message": "IP 情報の再読み込み"
112 | },
113 | "options-ipRules-textareaLabel": {
114 | "message": "IP 範囲 / アドレス"
115 | },
116 | "options-ipRules-textareaPlaceholder": {
117 | "message": "1回線あたり、1つの IP/IP 範囲"
118 | },
119 | "options-modal-askDelete": {
120 | "message": "このルールを削除してよろしいですか?"
121 | },
122 | "options-modal-askReset": {
123 | "message": "環境設定をリセットしても大丈夫ですか?"
124 | },
125 | "options-modal-confirmDelete": {
126 | "message": "はい、削除して下さい!"
127 | },
128 | "options-modal-confirmReset": {
129 | "message": "はい、私の環境設定をリセットして下さい!"
130 | },
131 | "options-settings": {
132 | "message": "環境設定"
133 | },
134 | "options-settings-import": {
135 | "message": "インポート"
136 | },
137 | "options-settings-importing": {
138 | "message": "環境設定をインポートする"
139 | },
140 | "options-settings-export": {
141 | "message": "エクスポート"
142 | },
143 | "options-settings-reset": {
144 | "message": "Default(既定値)にリセット"
145 | },
146 | "options-settings-permissions": {
147 | "message": "Chameleonは、フィンガープリントへの抵抗やトラッキング保護の有効化など、Firefoxのいくつかの設定を制御できます。これは他の拡張機能と競合する可能性があります。プライバシーの権限を削除することで、Chameleonがこれらの設定を制御することをオプトアウトできます。この権限を削除すると、これらの設定がリセットされることに注意してください。この権限が存在しない場合は、要求することができます。"
148 | },
149 | "options-settings-permissions-legacy": {
150 | "message": "プライバシー許可を有効にするには、Chameleon の特別なバージョンをインストールする必要があります。これは、クロスプラットフォーム/この拡張機能の新しいバージョンでサポートされている権限の複雑さによるものです。詳細については、以下にリンクされている wiki を参照してください。"
151 | },
152 | "options-settings-permissions-legacy-wiki": {
153 | "message": "wiki に詳細情報"
154 | },
155 | "options-settings-permissions-request": {
156 | "message": "プライバシー許可をリクエストする"
157 | },
158 | "options-settings-permissions-remove": {
159 | "message": "プライバシー許可を削除する"
160 | },
161 | "options-tab-about": {
162 | "message": "概要"
163 | },
164 | "options-tab-ipRules": {
165 | "message": "IP ルール"
166 | },
167 | "options-whitelist-acceptLang": {
168 | "message": "受け入れ言語"
169 | },
170 | "options-whitelist-editorTitle": {
171 | "message": "ホワイトリストのルールエディタ"
172 | },
173 | "options-whitelist-headerIPLabel": {
174 | "message": "IP ヘッダ (Via や X-Forwarded のための)"
175 | },
176 | "options-whitelist-options-audioContext": {
177 | "message": "Enable spoof audio context"
178 | },
179 | "options-whitelist-options-clientRects": {
180 | "message": "Enable spoof client rects"
181 | },
182 | "options-whitelist-options-cssExfil": {
183 | "message": "CSS Exfil をブロックする"
184 | },
185 | "options-whitelist-options-mediaDevices": {
186 | "message": "メディアデバイスをブロックする"
187 | },
188 | "options-whitelist-options-name": {
189 | "message": "window.name 保護を有効にする"
190 | },
191 | "options-whitelist-options-tz": {
192 | "message": "タイムゾーンのスプーフィング(偽装化)を有効にします。"
193 | },
194 | "options-whitelist-options-ws": {
195 | "message": "WebSocket を無効にする"
196 | },
197 | "options-whitelist-rule": {
198 | "message": "ホワイトリストのルール"
199 | },
200 | "options-whitelist-sitesTip": {
201 | "message": "1行に、1つのルール:ドメイン@@[任意の正規表現パターン]"
202 | },
203 | "options-whitelist-textareaLabel": {
204 | "message": "IP 範囲 / アドレス"
205 | },
206 | "options-whitelist-textareaPlaceholder": {
207 | "message": "1回線あたり、1つの IP/IP 範囲"
208 | },
209 | "popup-home-change": {
210 | "message": "変更"
211 | },
212 | "popup-home-currentProfile": {
213 | "message": "現在のプロファイル"
214 | },
215 | "popup-home-currentProfile-defaultLanguage": {
216 | "message": "既定の言語"
217 | },
218 | "popup-home-currentProfile-defaultScreen": {
219 | "message": "既定の画面"
220 | },
221 | "popup-home-currentProfile-defaultTimezone": {
222 | "message": "既定のタイムゾーン"
223 | },
224 | "popup-home-currentProfile-gettingTimezone": {
225 | "message": "IP 情報を取得する"
226 | },
227 | "popup-home-currentProfile-screenProfile": {
228 | "message": "画面 (プロファイル)"
229 | },
230 | "popup-home-disabled": {
231 | "message": "カメレオンは無効化状態です"
232 | },
233 | "popup-home-enabled": {
234 | "message": "カメレオンは有効化されている"
235 | },
236 | "popup-home-notification-disabled": {
237 | "message": "通知オフ"
238 | },
239 | "popup-home-notification-enabled": {
240 | "message": "通知オン"
241 | },
242 | "popup-home-onThisPage": {
243 | "message": "このページでは"
244 | },
245 | "popup-home-theme-dark": {
246 | "message": "ダーク(黒基調)"
247 | },
248 | "popup-home-theme-light": {
249 | "message": "ライト(白基調)"
250 | },
251 | "popup-profile-changePeriodically": {
252 | "message": "定期的に変更する"
253 | },
254 | "popup-profile-devicePhone": {
255 | "message": "電話"
256 | },
257 | "popup-profile-deviceTablet": {
258 | "message": "タブレット端末"
259 | },
260 | "popup-profile-interval-no": {
261 | "message": "不可"
262 | },
263 | "popup-profile-interval-custom": {
264 | "message": "カスタム間隔"
265 | },
266 | "popup-profile-interval-customMax": {
267 | "message": "最大 (分)"
268 | },
269 | "popup-profile-interval-customMin": {
270 | "message": "最小 (分)"
271 | },
272 | "popup-profile-interval-minute": {
273 | "message": "1 分ごと"
274 | },
275 | "popup-profile-interval-5minutes": {
276 | "message": "5 分ごと"
277 | },
278 | "popup-profile-interval-10minutes": {
279 | "message": "10 分ごと"
280 | },
281 | "popup-profile-interval-20minutes": {
282 | "message": "20 分ごと"
283 | },
284 | "popup-profile-interval-30minutes": {
285 | "message": "30 分ごと"
286 | },
287 | "popup-profile-interval-40minutes": {
288 | "message": "40 分ごと"
289 | },
290 | "popup-profile-interval-50minutes": {
291 | "message": "50 分ごと"
292 | },
293 | "popup-profile-interval-hour": {
294 | "message": "1 時間ごと"
295 | },
296 | "popup-profile-exclude": {
297 | "message": "除外"
298 | },
299 | "popup-profile-randomAndroid": {
300 | "message": "ランダムな Android Browsers"
301 | },
302 | "popup-profile-randomIOS": {
303 | "message": "ランダムな iOS Browsers"
304 | },
305 | "popup-profile-randomMacOS": {
306 | "message": "ランダムな macOS Browsers"
307 | },
308 | "popup-profile-randomLinux": {
309 | "message": "ランダムな Linux Browsers"
310 | },
311 | "popup-profile-randomWindows": {
312 | "message": "ランダムな Windows Browsers"
313 | },
314 | "popup-profile-random": {
315 | "message": "ランダム"
316 | },
317 | "popup-profile-randomDesktopProfile": {
318 | "message": "ランダムなプロファイル (Desktop)"
319 | },
320 | "popup-profile-randomMobileProfile": {
321 | "message": "ランダムなプロファイル (Mobile)"
322 | },
323 | "popup-profile-showProfileOnIcon": {
324 | "message": "Show browser profile on icon"
325 | },
326 | "popup-headers": {
327 | "message": "ヘッダー"
328 | },
329 | "popup-headers-enableDNT": {
330 | "message": "DNT (Do Not Track) を有効にする"
331 | },
332 | "popup-headers-preventEtag": {
333 | "message": "Etag トラッキングを防止する"
334 | },
335 | "popup-headers-refererWarning": {
336 | "message": "以下のオプションの about:config 設定を変更しないでください。"
337 | },
338 | "popup-headers-referer-trimming": {
339 | "message": "Trimming Policy のリファラー"
340 | },
341 | "popup-headers-referer-trimming-sendFullURI": {
342 | "message": "完全な URI を送信する (規定)"
343 | },
344 | "popup-headers-referer-trimming-schemeHostPortPath": {
345 | "message": "host, port + path のスキーム"
346 | },
347 | "popup-headers-referer-trimming-schemeHostPort": {
348 | "message": "host + port のスキーム"
349 | },
350 | "popup-headers-referer-xorigin": {
351 | "message": "X Origin Policy のリファラー"
352 | },
353 | "popup-headers-referer-xorigin-alwaysSend": {
354 | "message": "常に送る (規定)"
355 | },
356 | "popup-headers-referer-xorigin-matchBaseDomain": {
357 | "message": "基本ドメインと一致"
358 | },
359 | "popup-headers-referer-xorigin-matchHost": {
360 | "message": "host と一致"
361 | },
362 | "popup-headers-spoofAcceptLang": {
363 | "message": "Accept-Language を偽装する"
364 | },
365 | "popup-headers-spoofIP": {
366 | "message": "X-Forwarded-For/Via IP を偽装する"
367 | },
368 | "popup-headers-spoofIP-random": {
369 | "message": "ランダムな IP"
370 | },
371 | "popup-headers-spoofIP-custom": {
372 | "message": "カスタム IP"
373 | },
374 | "popup-headers-spoofIP-rangeFrom": {
375 | "message": "範囲の始点"
376 | },
377 | "popup-headers-spoofIP-rangeTo": {
378 | "message": "範囲の終点"
379 | },
380 | "popup-options": {
381 | "message": "オプション"
382 | },
383 | "popup-options-grantPermissions": {
384 | "message": "設定を変更する権限を付与する"
385 | },
386 | "popup-options-injection": {
387 | "message": "インジェクション"
388 | },
389 | "popup-options-injection-limitTabHistory": {
390 | "message": "タブ履歴を制限する"
391 | },
392 | "popup-options-injection-protectWinName": {
393 | "message": "関数 window.name(ウィンドウ名)を保護する"
394 | },
395 | "popup-options-injection-audioContext": {
396 | "message": "音声コンテキストを偽装する"
397 | },
398 | "popup-options-injection-clientRects": {
399 | "message": "Client Rects を偽装する"
400 | },
401 | "popup-options-injection-protectKBFingerprint": {
402 | "message": "キーボードの指紋を保護する"
403 | },
404 | "popup-options-injection-protectKBFingerprintDelay": {
405 | "message": "遅延 (ミリ秒)"
406 | },
407 | "popup-options-injection-screen": {
408 | "message": "画面サイズ"
409 | },
410 | "popup-options-injection-spoofFontFingerprint": {
411 | "message": "フォントの指紋を偽装する"
412 | },
413 | "popup-options-standard": {
414 | "message": "標準"
415 | },
416 | "popup-options-standard-blockMediaDevices": {
417 | "message": "メディアデバイスをブロックする"
418 | },
419 | "popup-options-standard-blockCSSExfil": {
420 | "message": "CSS Exfil をブロックする"
421 | },
422 | "popup-options-standard-disableWebRTC": {
423 | "message": "WebRTC を無効にする"
424 | },
425 | "popup-options-standard-firstPartyIsolation": {
426 | "message": "ファーストパーティ分離を有効にする"
427 | },
428 | "popup-options-standard-resistFingerprinting": {
429 | "message": "指紋採取を拒む機能を有効にします。"
430 | },
431 | "popup-options-standard-spoofMediaDevices": {
432 | "message": "メディアデバイスを偽装する"
433 | },
434 | "popup-options-standard-trackingProtection": {
435 | "message": "トラッキング保護モード"
436 | },
437 | "popup-options-standard-trackingProtection-on": {
438 | "message": "On"
439 | },
440 | "popup-options-standard-trackingProtection-off": {
441 | "message": "Off"
442 | },
443 | "popup-options-standard-trackingProtection-privateBrowsing": {
444 | "message": "プライベートブラウジングで有効"
445 | },
446 | "popup-options-standard-webRTCPolicy": {
447 | "message": "WebRTC ポリシー"
448 | },
449 | "popup-options-standard-webRTCPolicy-nonProxified": {
450 | "message": "プロキシされていない UDP を無効にする"
451 | },
452 | "popup-options-standard-webRTCPolicy-public": {
453 | "message": "パブリックインターフェースのみを使用する (最善)"
454 | },
455 | "popup-options-standard-webRTCPolicy-publicPrivate": {
456 | "message": "パブリックおよびプライベートインターフェイスを使用する"
457 | },
458 | "popup-options-standard-webSockets-blockAll": {
459 | "message": "全てブロック"
460 | },
461 | "popup-options-standard-webSockets-blockThirdParty": {
462 | "message": "第三者をブロック"
463 | },
464 | "popup-options-cookie": {
465 | "message": "クッキー"
466 | },
467 | "popup-options-cookieNotPersistent": {
468 | "message": "ウィンドウを閉じた後、クッキーとサイトデータを削除する"
469 | },
470 | "popup-options-cookiePolicy": {
471 | "message": "ポリシー"
472 | },
473 | "popup-options-cookiePolicy-allowVisited": {
474 | "message": "訪問を許可する"
475 | },
476 | "popup-options-cookiePolicy-rejectAll": {
477 | "message": "すべてを拒絶する"
478 | },
479 | "popup-options-cookiePolicy-rejectThirdParty": {
480 | "message": "第三者を拒絶する"
481 | },
482 | "popup-options-cookiePolicy-rejectTrackers": {
483 | "message": "トラッカーを拒絶する"
484 | },
485 | "popup-options-cookiePolicy-rejectTrackersPartitionForeign": {
486 | "message": "Reject trackers and partition third-party cookies"
487 | },
488 | "popup-whitelist-contextMenu": {
489 | "message": "ホワイトリストで現在のタブドメインを開くためのコンテキストメニュー項目を追加する"
490 | },
491 | "popup-whitelist-defaultProfileLabel": {
492 | "message": "既定のプロファイル"
493 | },
494 | "popup-whitelist-enable": {
495 | "message": "ホワイトリストの有効化"
496 | },
497 | "popup-whitelist-isNotWhitelisted": {
498 | "message": "ホワイトリストに登録されていません"
499 | },
500 | "popup-whitelist-isWhitelisted": {
501 | "message": "ホワイトリストに登録済み"
502 | },
503 | "popup-whitelist-open": {
504 | "message": "ホワイトリストで開く"
505 | },
506 | "text-addToRule": {
507 | "message": "Add to rule: $RULE_NAME$",
508 | "placeholders": {
509 | "rule_name": {
510 | "content": "$1",
511 | "example": "Firefox whitelist rule profile"
512 | }
513 | }
514 | },
515 | "text-allowAll": {
516 | "message": "すべて許可する"
517 | },
518 | "text-cancel": {
519 | "message": "取消"
520 | },
521 | "text-createNewRule": {
522 | "message": "新しいルールを作成する"
523 | },
524 | "text-default": {
525 | "message": "Default(規定)"
526 | },
527 | "text-defaultWhitelistProfile": {
528 | "message": "デフォルトのホワイトリストプロファイル"
529 | },
530 | "text-disableReferer": {
531 | "message": "リファラーを無効にする"
532 | },
533 | "text-language": {
534 | "message": "言語"
535 | },
536 | "text-name": {
537 | "message": "名称"
538 | },
539 | "text-profile": {
540 | "message": "プロファイル"
541 | },
542 | "text-realProfile": {
543 | "message": "真正プロファイル"
544 | },
545 | "text-removeFromRule": {
546 | "message": "Remove from rule: $RULE_NAME$",
547 | "placeholders": {
548 | "rule_name": {
549 | "content": "$1",
550 | "example": "Firefox whitelist rule profile"
551 | }
552 | }
553 | },
554 | "text-save": {
555 | "message": "保存する"
556 | },
557 | "text-screen": {
558 | "message": "画面"
559 | },
560 | "text-searchRules": {
561 | "message": "検索ルール"
562 | },
563 | "text-startupDelay": {
564 | "message": "Startup delay (sec)"
565 | },
566 | "text-timezone": {
567 | "message": "タイムゾーン"
568 | },
569 | "text-whitelist": {
570 | "message": "ホワイトリスト"
571 | }
572 | }
573 |
--------------------------------------------------------------------------------
/src/_locales/zh_CN/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "欺骗您的浏览器配置文件。包括一些隐私增强选项。"
4 | },
5 | "notifications-profileChange": {
6 | "message": "配置文件已更改:"
7 | },
8 | "notifications-unableToGetIPInfo": {
9 | "message": "无法获取IP信息"
10 | },
11 | "notifications-usingIPInfo": {
12 | "message": "使用IP信息:"
13 | },
14 | "notifications-usingIPRule": {
15 | "message": "使用IP规则:"
16 | },
17 | "options-about-issueTracker": {
18 | "message": "问题跟踪"
19 | },
20 | "options-about-knownIssues": {
21 | "message": "已知问题"
22 | },
23 | "options-about-wiki": {
24 | "message": "Wiki"
25 | },
26 | "options-about-support": {
27 | "message": "支持"
28 | },
29 | "options-about-sourceCode": {
30 | "message": "源码"
31 | },
32 | "options-about-translate": {
33 | "message": "帮助翻译"
34 | },
35 | "options-import-couldNotImport": {
36 | "message": "无法导入文件"
37 | },
38 | "options-import-invalid-config": {
39 | "message": "设置无效:缺少配置"
40 | },
41 | "options-import-invalid-excluded": {
42 | "message": "设置无效:缺少排除项"
43 | },
44 | "options-import-invalid-excludedProfile": {
45 | "message": "无效设置:排除项包含无效的配置文件"
46 | },
47 | "options-import-invalid-headers": {
48 | "message": "设置无效:缺少头"
49 | },
50 | "options-import-invalid-ipRuleId": {
51 | "message": "设置无效:IP规则id无效"
52 | },
53 | "options-import-invalid-ipRuleName": {
54 | "message": "设置无效:缺少IP规则名称"
55 | },
56 | "options-import-invalid-ipRuleRange": {
57 | "message": "设置无效:IP规则范围无效"
58 | },
59 | "options-import-invalid-ipRules": {
60 | "message": "设置无效:缺少IP规则"
61 | },
62 | "options-import-invalid-ipRulesDupe": {
63 | "message": "设置无效:发现重复的IP规则id"
64 | },
65 | "options-import-invalid-options": {
66 | "message": "设置无效:缺少选项"
67 | },
68 | "options-import-invalid-profile": {
69 | "message": "设置无效:缺少配置设置"
70 | },
71 | "options-import-invalid-setting": {
72 | "message": "设置值无效:"
73 | },
74 | "options-import-invalid-settings": {
75 | "message": "设置无效:缺少设置"
76 | },
77 | "options-import-invalid-spoofIP": {
78 | "message": "设置无效:欺骗头部IP范围无效"
79 | },
80 | "options-import-invalid-version": {
81 | "message": "设置无效:版本不被接受"
82 | },
83 | "options-import-invalid-whitelist": {
84 | "message": "设置无效:缺少白名单"
85 | },
86 | "options-import-invalid-whitelistDupe": {
87 | "message": "设置无效:发现重复的白名单规则id"
88 | },
89 | "options-import-invalid-whitelistId": {
90 | "message": "设置无效:白名单规则id无效"
91 | },
92 | "options-import-invalid-whitelistName": {
93 | "message": "设置无效:缺少白名单规则名称"
94 | },
95 | "options-import-invalid-whitelistOpt": {
96 | "message": "设置无效:白名单规则选项无效"
97 | },
98 | "options-import-invalid-whitelistSpoofIP": {
99 | "message": "设置无效:白名单规则的欺骗IP无效"
100 | },
101 | "options-import-success": {
102 | "message": "导入设置成功。正在重新加载扩展..."
103 | },
104 | "options-ipRules-editorTitle": {
105 | "message": "IP规则编辑器"
106 | },
107 | "options-ipRules-ipRule": {
108 | "message": "IP规则"
109 | },
110 | "options-ipRules-reload": {
111 | "message": "重新加载IP信息"
112 | },
113 | "options-ipRules-textareaLabel": {
114 | "message": "IP范围/地址"
115 | },
116 | "options-ipRules-textareaPlaceholder": {
117 | "message": "每行一个IP/IP范围"
118 | },
119 | "options-modal-askDelete": {
120 | "message": "您确定要删除这条规则吗?"
121 | },
122 | "options-modal-askReset": {
123 | "message": "您确定要重置您的设置?"
124 | },
125 | "options-modal-confirmDelete": {
126 | "message": "是的,将其删除!"
127 | },
128 | "options-modal-confirmReset": {
129 | "message": "是,重置我的设置!"
130 | },
131 | "options-settings": {
132 | "message": "设置"
133 | },
134 | "options-settings-import": {
135 | "message": "导入"
136 | },
137 | "options-settings-importing": {
138 | "message": "正在导入设置"
139 | },
140 | "options-settings-export": {
141 | "message": "导出"
142 | },
143 | "options-settings-reset": {
144 | "message": "重置为默认"
145 | },
146 | "options-settings-permissions": {
147 | "message": "Chameleon可以控制一些Firefox首选项,如抵制指纹识别或启用跟踪保护。这可能会与其他扩展冲突。您可以通过移除隐私权限退出Chameleon对这些首选项的控制。请注意,移除此权限将会重置这些首选项。如果不存在,您可以请求此权限。"
148 | },
149 | "options-settings-permissions-legacy": {
150 | "message": "您需要安装Chameleon的专用版本以启用隐私权限。这是因为支持跨平台/新版的扩展的权限有些复杂的问题。更多详细信息可在下方链接的wiki找到。"
151 | },
152 | "options-settings-permissions-legacy-wiki": {
153 | "message": "更多信息请查看wiki"
154 | },
155 | "options-settings-permissions-request": {
156 | "message": "请求隐私权限"
157 | },
158 | "options-settings-permissions-remove": {
159 | "message": "移除隐私权限"
160 | },
161 | "options-tab-about": {
162 | "message": "关于"
163 | },
164 | "options-tab-ipRules": {
165 | "message": "IP规则"
166 | },
167 | "options-whitelist-acceptLang": {
168 | "message": "Accept-Language"
169 | },
170 | "options-whitelist-editorTitle": {
171 | "message": "白名单规则编辑器"
172 | },
173 | "options-whitelist-headerIPLabel": {
174 | "message": "头部IP(Via & X-Forwarded-For)"
175 | },
176 | "options-whitelist-options-audioContext": {
177 | "message": "启用欺骗音频上下文"
178 | },
179 | "options-whitelist-options-clientRects": {
180 | "message": "启用欺骗客户端矩形"
181 | },
182 | "options-whitelist-options-cssExfil": {
183 | "message": "阻止CSS Exfil"
184 | },
185 | "options-whitelist-options-mediaDevices": {
186 | "message": "阻止媒体设备"
187 | },
188 | "options-whitelist-options-name": {
189 | "message": "启用保护窗口名称"
190 | },
191 | "options-whitelist-options-tz": {
192 | "message": "启用时区欺骗"
193 | },
194 | "options-whitelist-options-ws": {
195 | "message": "禁用WebSocket"
196 | },
197 | "options-whitelist-rule": {
198 | "message": "白名单规则"
199 | },
200 | "options-whitelist-sitesTip": {
201 | "message": "每行一条规则:域名@@[可选正则表达式]"
202 | },
203 | "options-whitelist-textareaLabel": {
204 | "message": "IP范围/地址"
205 | },
206 | "options-whitelist-textareaPlaceholder": {
207 | "message": "每行一个IP/IP范围"
208 | },
209 | "popup-home-change": {
210 | "message": "更改"
211 | },
212 | "popup-home-currentProfile": {
213 | "message": "当前配置文件"
214 | },
215 | "popup-home-currentProfile-defaultLanguage": {
216 | "message": "默认语言"
217 | },
218 | "popup-home-currentProfile-defaultScreen": {
219 | "message": "默认屏幕"
220 | },
221 | "popup-home-currentProfile-defaultTimezone": {
222 | "message": "默认时区"
223 | },
224 | "popup-home-currentProfile-gettingTimezone": {
225 | "message": "正在获取IP信息"
226 | },
227 | "popup-home-currentProfile-screenProfile": {
228 | "message": "屏幕(配置文件)"
229 | },
230 | "popup-home-disabled": {
231 | "message": "Chameleon已禁用"
232 | },
233 | "popup-home-enabled": {
234 | "message": "Chameleon已启用"
235 | },
236 | "popup-home-notification-disabled": {
237 | "message": "通知关"
238 | },
239 | "popup-home-notification-enabled": {
240 | "message": "通知开"
241 | },
242 | "popup-home-onThisPage": {
243 | "message": "在本页面"
244 | },
245 | "popup-home-theme-dark": {
246 | "message": "深色"
247 | },
248 | "popup-home-theme-light": {
249 | "message": "浅色"
250 | },
251 | "popup-profile-changePeriodically": {
252 | "message": "定期更改"
253 | },
254 | "popup-profile-devicePhone": {
255 | "message": "手机"
256 | },
257 | "popup-profile-deviceTablet": {
258 | "message": "平板"
259 | },
260 | "popup-profile-interval-no": {
261 | "message": "否"
262 | },
263 | "popup-profile-interval-custom": {
264 | "message": "自定义间隔"
265 | },
266 | "popup-profile-interval-customMax": {
267 | "message": "最大值(分钟):"
268 | },
269 | "popup-profile-interval-customMin": {
270 | "message": "最小值(分钟):"
271 | },
272 | "popup-profile-interval-minute": {
273 | "message": "每分钟"
274 | },
275 | "popup-profile-interval-5minutes": {
276 | "message": "每5分钟"
277 | },
278 | "popup-profile-interval-10minutes": {
279 | "message": "每10分钟"
280 | },
281 | "popup-profile-interval-20minutes": {
282 | "message": "每20分钟"
283 | },
284 | "popup-profile-interval-30minutes": {
285 | "message": "每30分钟"
286 | },
287 | "popup-profile-interval-40minutes": {
288 | "message": "每40分钟"
289 | },
290 | "popup-profile-interval-50minutes": {
291 | "message": "每50分钟"
292 | },
293 | "popup-profile-interval-hour": {
294 | "message": "每小时"
295 | },
296 | "popup-profile-exclude": {
297 | "message": "排除"
298 | },
299 | "popup-profile-randomAndroid": {
300 | "message": "随机Android浏览器"
301 | },
302 | "popup-profile-randomIOS": {
303 | "message": "随机的iOS浏览器"
304 | },
305 | "popup-profile-randomMacOS": {
306 | "message": "随机macOS浏览器"
307 | },
308 | "popup-profile-randomLinux": {
309 | "message": "随机Linux浏览器"
310 | },
311 | "popup-profile-randomWindows": {
312 | "message": "随机Windows浏览器"
313 | },
314 | "popup-profile-random": {
315 | "message": "随机"
316 | },
317 | "popup-profile-randomDesktopProfile": {
318 | "message": "随机配置文件(桌面)"
319 | },
320 | "popup-profile-randomMobileProfile": {
321 | "message": "随机配置文件(移动)"
322 | },
323 | "popup-profile-showProfileOnIcon": {
324 | "message": "在图标上显示浏览器配置文件"
325 | },
326 | "popup-headers": {
327 | "message": "头信息"
328 | },
329 | "popup-headers-enableDNT": {
330 | "message": "启用DNT(Do Not Track)"
331 | },
332 | "popup-headers-preventEtag": {
333 | "message": "阻止Etag跟踪"
334 | },
335 | "popup-headers-refererWarning": {
336 | "message": "请勿修改以下选项的about:config设置。"
337 | },
338 | "popup-headers-referer-trimming": {
339 | "message": "Referer微调政策"
340 | },
341 | "popup-headers-referer-trimming-sendFullURI": {
342 | "message": "发送完整URI(默认)"
343 | },
344 | "popup-headers-referer-trimming-schemeHostPortPath": {
345 | "message": "方案、主机、端口+路径"
346 | },
347 | "popup-headers-referer-trimming-schemeHostPort": {
348 | "message": "方案、主机+端口"
349 | },
350 | "popup-headers-referer-xorigin": {
351 | "message": "Referer X Origin策略"
352 | },
353 | "popup-headers-referer-xorigin-alwaysSend": {
354 | "message": "总是发送(默认)"
355 | },
356 | "popup-headers-referer-xorigin-matchBaseDomain": {
357 | "message": "匹配基本域名"
358 | },
359 | "popup-headers-referer-xorigin-matchHost": {
360 | "message": "匹配主机"
361 | },
362 | "popup-headers-spoofAcceptLang": {
363 | "message": "欺骗Accept-Language"
364 | },
365 | "popup-headers-spoofIP": {
366 | "message": "欺骗X-Forwarded-For/Via IP"
367 | },
368 | "popup-headers-spoofIP-random": {
369 | "message": "随机IP"
370 | },
371 | "popup-headers-spoofIP-custom": {
372 | "message": "自定义IP"
373 | },
374 | "popup-headers-spoofIP-rangeFrom": {
375 | "message": "来源范围"
376 | },
377 | "popup-headers-spoofIP-rangeTo": {
378 | "message": "目标范围"
379 | },
380 | "popup-options": {
381 | "message": "选项"
382 | },
383 | "popup-options-grantPermissions": {
384 | "message": "授予修改设置的权限"
385 | },
386 | "popup-options-injection": {
387 | "message": "注入"
388 | },
389 | "popup-options-injection-limitTabHistory": {
390 | "message": "限制标签页历史"
391 | },
392 | "popup-options-injection-protectWinName": {
393 | "message": "保护窗口名称"
394 | },
395 | "popup-options-injection-audioContext": {
396 | "message": "欺骗音频上下文"
397 | },
398 | "popup-options-injection-clientRects": {
399 | "message": "欺骗客户端矩形"
400 | },
401 | "popup-options-injection-protectKBFingerprint": {
402 | "message": "保护键盘指纹"
403 | },
404 | "popup-options-injection-protectKBFingerprintDelay": {
405 | "message": "延迟(毫秒)"
406 | },
407 | "popup-options-injection-screen": {
408 | "message": "屏幕大小"
409 | },
410 | "popup-options-injection-spoofFontFingerprint": {
411 | "message": "欺骗字体指纹"
412 | },
413 | "popup-options-standard": {
414 | "message": "标准"
415 | },
416 | "popup-options-standard-blockMediaDevices": {
417 | "message": "阻止媒体设备"
418 | },
419 | "popup-options-standard-blockCSSExfil": {
420 | "message": "阻止CSS Exfil"
421 | },
422 | "popup-options-standard-disableWebRTC": {
423 | "message": "禁用WebRTC"
424 | },
425 | "popup-options-standard-firstPartyIsolation": {
426 | "message": "启用第一方隔离"
427 | },
428 | "popup-options-standard-resistFingerprinting": {
429 | "message": "抵制指纹识别"
430 | },
431 | "popup-options-standard-spoofMediaDevices": {
432 | "message": "欺骗媒体设备"
433 | },
434 | "popup-options-standard-trackingProtection": {
435 | "message": "跟踪保护模式"
436 | },
437 | "popup-options-standard-trackingProtection-on": {
438 | "message": "开"
439 | },
440 | "popup-options-standard-trackingProtection-off": {
441 | "message": "关"
442 | },
443 | "popup-options-standard-trackingProtection-privateBrowsing": {
444 | "message": "在隐私浏览中启用"
445 | },
446 | "popup-options-standard-webRTCPolicy": {
447 | "message": "WebRTC策略"
448 | },
449 | "popup-options-standard-webRTCPolicy-nonProxified": {
450 | "message": "禁用非代理的UDP"
451 | },
452 | "popup-options-standard-webRTCPolicy-public": {
453 | "message": "仅使用公共接口(最佳)"
454 | },
455 | "popup-options-standard-webRTCPolicy-publicPrivate": {
456 | "message": "使用公共和私有接口"
457 | },
458 | "popup-options-standard-webSockets-blockAll": {
459 | "message": "全部阻止"
460 | },
461 | "popup-options-standard-webSockets-blockThirdParty": {
462 | "message": "阻止第三方"
463 | },
464 | "popup-options-cookie": {
465 | "message": "Cookie选项"
466 | },
467 | "popup-options-cookieNotPersistent": {
468 | "message": "窗口关闭后删除cookie和站点数据"
469 | },
470 | "popup-options-cookiePolicy": {
471 | "message": "策略"
472 | },
473 | "popup-options-cookiePolicy-allowVisited": {
474 | "message": "允许访问过的第三方"
475 | },
476 | "popup-options-cookiePolicy-rejectAll": {
477 | "message": "全部阻止"
478 | },
479 | "popup-options-cookiePolicy-rejectThirdParty": {
480 | "message": "阻止第三方"
481 | },
482 | "popup-options-cookiePolicy-rejectTrackers": {
483 | "message": "拒绝跟踪器"
484 | },
485 | "popup-options-cookiePolicy-rejectTrackersPartitionForeign": {
486 | "message": "阻隔跟踪器和第三方cookie"
487 | },
488 | "popup-whitelist-contextMenu": {
489 | "message": "添加右键菜单条目到当前打开标签页白名单中的域名"
490 | },
491 | "popup-whitelist-defaultProfileLabel": {
492 | "message": "默认配置文件"
493 | },
494 | "popup-whitelist-enable": {
495 | "message": "启用白名单"
496 | },
497 | "popup-whitelist-isNotWhitelisted": {
498 | "message": "未加入白名单"
499 | },
500 | "popup-whitelist-isWhitelisted": {
501 | "message": "已加入白名单"
502 | },
503 | "popup-whitelist-open": {
504 | "message": "在白名单中打开"
505 | },
506 | "text-addToRule": {
507 | "message": "添加到规则:$RULE_NAME$",
508 | "placeholders": {
509 | "rule_name": {
510 | "content": "$1",
511 | "example": "Firefox whitelist rule profile"
512 | }
513 | }
514 | },
515 | "text-allowAll": {
516 | "message": "全部允许"
517 | },
518 | "text-cancel": {
519 | "message": "取消"
520 | },
521 | "text-createNewRule": {
522 | "message": "创建新规则"
523 | },
524 | "text-default": {
525 | "message": "默认"
526 | },
527 | "text-defaultWhitelistProfile": {
528 | "message": "默认白名单配置文件"
529 | },
530 | "text-disableReferer": {
531 | "message": "禁用Referer"
532 | },
533 | "text-language": {
534 | "message": "语言"
535 | },
536 | "text-name": {
537 | "message": "名称"
538 | },
539 | "text-profile": {
540 | "message": "配置文件"
541 | },
542 | "text-realProfile": {
543 | "message": "真实配置文件"
544 | },
545 | "text-removeFromRule": {
546 | "message": "从规则中移除:$RULE_NAME$",
547 | "placeholders": {
548 | "rule_name": {
549 | "content": "$1",
550 | "example": "Firefox whitelist rule profile"
551 | }
552 | }
553 | },
554 | "text-save": {
555 | "message": "保存"
556 | },
557 | "text-screen": {
558 | "message": "屏幕"
559 | },
560 | "text-searchRules": {
561 | "message": "搜索规则"
562 | },
563 | "text-startupDelay": {
564 | "message": "启动延迟(秒)"
565 | },
566 | "text-timezone": {
567 | "message": "时区"
568 | },
569 | "text-whitelist": {
570 | "message": "已加入白名单"
571 | }
572 | }
573 |
--------------------------------------------------------------------------------
/src/background.ts:
--------------------------------------------------------------------------------
1 | import { Chameleon } from './lib/chameleon';
2 | import store from './store';
3 | import webext from './lib/webext';
4 |
5 | webext.firstTimeInstall();
6 |
7 | store.state.version = browser.runtime.getManifest().version;
8 |
9 | let chameleon = new Chameleon(JSON.parse(JSON.stringify(store.state)));
10 | let messageHandler = (request: any, sender: any, sendResponse: any) => {
11 | if (request.action === 'save') {
12 | if (chameleon.timeout) {
13 | clearTimeout(chameleon.timeout);
14 | }
15 |
16 | chameleon.settings = Object.assign(chameleon.settings, request.data);
17 | chameleon.timeout = setTimeout(() => {
18 | chameleon.saveSettings(request.data);
19 | sendResponse('done');
20 | }, 200);
21 | } else if (request.action === 'implicitSave') {
22 | if (chameleon.timeout) {
23 | clearTimeout(chameleon.timeout);
24 | }
25 |
26 | chameleon.timeout = setTimeout(() => {
27 | chameleon.saveSettings(chameleon.settings);
28 | sendResponse('done');
29 | }, 200);
30 | } else if (request.action === 'contextMenu') {
31 | chameleon.toggleContextMenu(request.data);
32 | } else if (request.action === 'toggleBadgeText') {
33 | chameleon.updateBadgeText(request.data);
34 | } else if (request.action === 'getSettings') {
35 | (async () => {
36 | if (!!browser.privacy) {
37 | let cookieSettings = await browser.privacy.websites.cookieConfig.get({});
38 | chameleon.settings.options.cookieNotPersistent = cookieSettings.value.nonPersistentCookies;
39 | chameleon.settings.options.cookiePolicy = cookieSettings.value.behavior;
40 |
41 | let firstPartyIsolate = await browser.privacy.websites.firstPartyIsolate.get({});
42 | chameleon.settings.options.firstPartyIsolate = firstPartyIsolate.value;
43 |
44 | let resistFingerprinting = await browser.privacy.websites.resistFingerprinting.get({});
45 | chameleon.settings.options.resistFingerprinting = resistFingerprinting.value;
46 |
47 | let trackingProtectionMode = await browser.privacy.websites.trackingProtectionMode.get({});
48 | chameleon.settings.options.trackingProtectionMode = trackingProtectionMode.value;
49 |
50 | let peerConnectionEnabled = await browser.privacy.network.peerConnectionEnabled.get({});
51 | chameleon.settings.options.disableWebRTC = !peerConnectionEnabled.value;
52 |
53 | let webRTCIPHandlingPolicy = await browser.privacy.network.webRTCIPHandlingPolicy.get({});
54 | chameleon.settings.options.webRTCPolicy = webRTCIPHandlingPolicy.value;
55 | }
56 |
57 | let settings = Object.assign({}, chameleon.settings);
58 | settings.config.hasPrivacyPermission = !!browser.privacy;
59 |
60 | sendResponse(settings);
61 | })();
62 | } else if (request.action === 'init') {
63 | browser.runtime.sendMessage(
64 | {
65 | action: 'tempStore',
66 | data: chameleon.tempStore,
67 | },
68 | response => {
69 | if (browser.runtime.lastError) return;
70 | }
71 | );
72 | sendResponse('done');
73 | } else if (request.action === 'reloadInjectionScript') {
74 | chameleon.buildInjectionScript();
75 | sendResponse('done');
76 | } else if (request.action === 'reloadIPInfo') {
77 | if (chameleon.settings.options.timeZone === 'ip' || (chameleon.settings.headers.spoofAcceptLang.value === 'ip' && chameleon.settings.headers.spoofAcceptLang.enabled)) {
78 | chameleon.updateIPInfo();
79 | sendResponse('done');
80 | }
81 | } else if (request.action === 'reloadProfile') {
82 | chameleon.setTimer(request.data);
83 | chameleon.buildInjectionScript();
84 | sendResponse('done');
85 | } else if (request.action === 'reloadSpoofIP') {
86 | if (request.data[0].name === 'headers.spoofIP.enabled') {
87 | chameleon.settings.headers.spoofIP.enabled = request.data[0].value;
88 | } else if (request.data[0].name === 'headers.spoofIP.option') {
89 | chameleon.settings.headers.spoofIP.option = request.data[0].value;
90 | } else if (request.data[0].name === 'headers.spoofIP.rangeFrom') {
91 | chameleon.settings.headers.spoofIP.rangeFrom = request.data[0].value;
92 | chameleon.settings.headers.spoofIP.rangeTo = request.data[1].value;
93 | }
94 |
95 | chameleon.updateSpoofIP();
96 | sendResponse('done');
97 | } else if (request.action === 'reset') {
98 | chameleon.reset();
99 | browser.runtime.reload();
100 | } else if (request.action === 'updateIPRules') {
101 | chameleon.settings.ipRules = request.data;
102 |
103 | chameleon.timeout = setTimeout(() => {
104 | chameleon.saveSettings(chameleon.settings);
105 | sendResponse('done');
106 | }, 200);
107 | } else if (request.action === 'updateProfile') {
108 | chameleon.settings.profile.selected = request.data;
109 |
110 | // reset interval timer and send notification
111 | chameleon.setTimer();
112 | sendResponse('done');
113 | } else if (request.action === 'updateWhitelist') {
114 | chameleon.settings.whitelist = request.data;
115 | chameleon.updateProfileCache();
116 |
117 | chameleon.timeout = setTimeout(() => {
118 | chameleon.saveSettings(chameleon.settings);
119 | sendResponse('done');
120 | }, 200);
121 | } else if (request.action === 'validateSettings') {
122 | let res = chameleon.validateSettings(request.data);
123 | sendResponse(res);
124 | }
125 |
126 | return true;
127 | };
128 |
129 | browser.alarms.onAlarm.addListener(() => {
130 | chameleon.run();
131 | });
132 |
133 | browser.runtime.onMessage.addListener(messageHandler);
134 |
135 | (async () => {
136 | await chameleon.init(await webext.getSettings(null));
137 |
138 | if (chameleon.settings.options.timeZone === 'ip' || (chameleon.settings.headers.spoofAcceptLang.value === 'ip' && chameleon.settings.headers.spoofAcceptLang.enabled)) {
139 | setTimeout(() => {
140 | chameleon.updateIPInfo();
141 | }, chameleon.settings.config.reloadIPStartupDelay * 1000);
142 | }
143 |
144 | if (!!browser.privacy) {
145 | await chameleon.changeBrowserSettings();
146 | }
147 |
148 | chameleon.setupHeaderListeners();
149 | chameleon.setTimer();
150 |
151 | webext.enableChameleon(chameleon.settings.config.enabled);
152 | chameleon.toggleContextMenu(chameleon.settings.whitelist.enabledContextMenu);
153 |
154 | /*
155 | Allow Chameleon to be controlled by another extension
156 |
157 | Enabled only in developer builds
158 | */
159 | if (browser.runtime.getManifest().version_name.includes('-')) {
160 | browser.runtime.onMessageExternal.addListener((request, sender, sendResponse) => {
161 | messageHandler(request, sender, sendResponse);
162 |
163 | return true;
164 | });
165 | }
166 |
167 | if (chameleon.platform.os != 'android') {
168 | browser.browserAction.setBadgeBackgroundColor({
169 | color: 'green',
170 | });
171 | }
172 | })();
173 |
--------------------------------------------------------------------------------
/src/browser.d.ts:
--------------------------------------------------------------------------------
1 | declare var browser: any;
2 |
3 | declare module 'browser' {
4 | export = browser;
5 | }
6 |
--------------------------------------------------------------------------------
/src/css/notosans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sereneblue/chameleon/1e620117b5c183f4e7bed50d533190b5e63fc152/src/css/notosans.ttf
--------------------------------------------------------------------------------
/src/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 |
4 | @font-face {
5 | font-family: 'Noto Sans';
6 | src: url('notosans.ttf') format('truetype');
7 | }
8 |
9 | * {
10 | margin: 0;
11 | font-family: 'Noto Sans', sans-serif !important;
12 | }
13 |
14 | .bg-light {
15 | @apply text-dark;
16 | }
17 |
18 | .bg-light .fg {
19 | @apply bg-light-fg;
20 | }
21 |
22 | .bg-dark {
23 | @apply text-light;
24 | }
25 |
26 | .bg-dark .fg {
27 | @apply bg-dark-fg;
28 | }
29 |
30 | .bg-dark .cursor-pointer.fg:hover,
31 | .bg-dark .group.fg:hover {
32 | @apply bg-dark-fg-alt;
33 | }
34 |
35 | .bg-light .cursor-pointer.fg:hover,
36 | .bg-light .group.fg:hover {
37 | @apply bg-light-fg-alt;
38 | }
39 |
40 | .group {
41 | @apply flex-grow items-center px-2 py-1 mb-1;
42 | }
43 |
44 | .group.active {
45 | @apply bg-primary text-light;
46 | }
47 | .group.active.fg:hover {
48 | @apply bg-primary text-light;
49 | }
50 |
51 | .group-options {
52 | @apply flex flex-1 justify-center cursor-pointer;
53 | }
54 |
55 | .group-options.fg:hover {
56 | @apply bg-dark-fg-alt;
57 | }
58 |
59 | .group-options.fg:hover {
60 | @apply bg-light-fg-alt;
61 | }
62 |
63 | .profile-item {
64 | @apply -mx-3 px-3 flex justify-between py-1;
65 | }
66 |
67 | .profile-item.bg-dark-fg:hover {
68 | @apply bg-dark-fg-alt;
69 | }
70 |
71 | .profile-item.bg-light-fg:hover {
72 | @apply bg-light-fg-alt;
73 | }
74 |
75 | .tab {
76 | @apply px-2 pt-2 pb-1 cursor-pointer text-light;
77 | }
78 |
79 | .app.bg-light .tab.active {
80 | @apply text-primary;
81 | }
82 |
83 | .app.bg-dark .tab.active {
84 | @apply text-light;
85 | }
86 |
87 | .options-tab {
88 | @apply px-4 py-1 cursor-pointer text-light;
89 | }
90 |
91 | .bg-light .options-tab.active {
92 | @apply text-primary;
93 | background-color: #fbfbfb;
94 | }
95 |
96 | .bg-dark .options-tab.active {
97 | @apply text-light;
98 | background-color: #33313b;
99 | }
100 |
101 | .transparent-btn {
102 | @apply bg-transparent py-2 px-4 border border-primary-soft rounded block mb-4 mr-4;
103 | }
104 |
105 | .transparent-btn:hover {
106 | @apply bg-primary-soft text-white border-transparent;
107 | }
108 |
109 | input,
110 | select {
111 | @apply text-dark;
112 | }
113 |
114 | .bg-dark input.error,
115 | .bg-dark textarea.error {
116 | @apply bg-red-300;
117 | }
118 |
119 | .bg-light input.error,
120 | .bg-light textarea.error {
121 | @apply bg-red-200;
122 | }
123 |
124 | .bg-dark button.bg-transparent {
125 | @apply text-light;
126 | }
127 |
128 | .bg-light button.bg-transparent {
129 | @apply text-primary;
130 | }
131 |
132 | .bg-light button.bg-transparent:hover {
133 | @apply text-light;
134 | }
135 |
136 | .bg-dark #iprules tbody tr {
137 | @apply border-dark-fg-alt;
138 | }
139 |
140 | .bg-dark #iprules tbody tr:hover {
141 | @apply bg-dark-fg;
142 | }
143 |
144 | .bg-light #iprules tbody tr {
145 | @apply border-light-fg-alt;
146 | }
147 |
148 | .bg-light #iprules tbody tr:hover {
149 | @apply bg-light-fg;
150 | }
151 |
152 | .modal {
153 | @apply m-auto shadow-xl text-dark rounded-lg;
154 | }
155 |
156 | /* cancel button */
157 | .modal button.bg-transparent {
158 | @apply text-gray-600;
159 | }
160 |
161 | .modal button.bg-transparent:hover {
162 | @apply text-gray-700;
163 | }
164 |
165 | .bg-dark .modal {
166 | @apply bg-gray-400;
167 | }
168 |
169 | .bg-light .modal {
170 | background-color: #fbfbfb;
171 | }
172 |
173 | /* Checkbox switch */
174 | .form-switch {
175 | @apply relative select-none w-12 mr-1 leading-normal;
176 | }
177 | .form-switch-checkbox {
178 | @apply hidden;
179 | }
180 | .form-switch-label {
181 | @apply block overflow-hidden cursor-pointer bg-gray-300 border rounded-full h-6 shadow-inner;
182 |
183 | transition: background-color 0.2s ease-in;
184 | }
185 | .form-switch-label:before {
186 | @apply absolute block bg-white inset-y-0 w-6 border-2 rounded-full -ml-1;
187 |
188 | right: 50%;
189 | content: '';
190 | transition: all 0.2s ease-in;
191 | }
192 | .form-switch-checkbox:checked + .form-switch-label,
193 | .form-switch-checkbox:checked + .form-switch-label:before {
194 | }
195 | .form-switch-checkbox:checked + .form-switch-label {
196 | @apply bg-primary shadow-none;
197 | }
198 | .form-switch-checkbox:checked + .form-switch-label:before {
199 | @apply inset-y-0 right-0;
200 | }
201 |
202 | .bg-dark #detected .fp {
203 | @apply bg-dark-fg-alt;
204 | }
205 |
206 | .bg-light #detected .fp {
207 | @apply bg-light-fg-alt text-black;
208 | }
209 |
210 | #detected .fp {
211 | @apply flex items-center justify-center rounded-lg p-1 border-primary w-10 shadow-xs;
212 | }
213 |
214 | #detected .fp.active {
215 | @apply bg-primary text-white;
216 | }
217 |
218 | .fp div {
219 | opacity: 0%;
220 | @apply absolute transition transition-opacity duration-300 ease-in-out flex justify-center -mt-24 px-4 text-black z-20 rounded text-sm bg-yellow-200 border border-yellow-600 p-1 shadow-sm;
221 | }
222 |
223 | .fp:hover div {
224 | opacity: 100%;
225 | }
226 |
227 | @tailwind utilities;
228 |
--------------------------------------------------------------------------------
/src/icons/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/icon_disabled.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/devices.ts:
--------------------------------------------------------------------------------
1 | interface Device {
2 | name: string;
3 | build: string;
4 | viewport: string;
5 | deviceScaleFactor: number;
6 | memory?: number;
7 | hw?: number;
8 | }
9 |
10 | let devices: any = {
11 | mobile: {
12 | and1: [
13 | {
14 | name: 'Google Pixel 5',
15 | build: 'Pixel 5',
16 | viewport: '393x851',
17 | deviceScaleFactor: 2.75,
18 | memory: 8,
19 | hw: 8,
20 | },
21 | {
22 | name: 'Moto G9 Play',
23 | build: 'moto g(9) play',
24 | viewport: '412x915',
25 | deviceScaleFactor: 1.75,
26 | memory: 4,
27 | hw: 8,
28 | },
29 | {
30 | name: 'OnePlus 8',
31 | build: 'IN2013',
32 | viewport: '412x915',
33 | deviceScaleFactor: 2.625,
34 | memory: 8,
35 | hw: 8,
36 | },
37 | {
38 | name: 'Samsung Galaxy A52',
39 | build: 'SM-A525F',
40 | viewport: '412x915',
41 | deviceScaleFactor: 2.625,
42 | memory: 4,
43 | hw: 8,
44 | },
45 | {
46 | name: 'Samsung Galaxy S21',
47 | build: 'SM-G991B',
48 | viewport: '360x800',
49 | deviceScaleFactor: 3,
50 | memory: 8,
51 | hw: 8,
52 | },
53 | ],
54 | and2: [
55 | {
56 | name: 'Google Pixel 6',
57 | build: 'Pixel 6',
58 | viewport: '412x915',
59 | deviceScaleFactor: 2.625,
60 | memory: 8,
61 | hw: 8,
62 | },
63 | {
64 | name: 'Redmi Note 12 Pro',
65 | build: '22101316I',
66 | viewport: '393x873',
67 | deviceScaleFactor: 2.75,
68 | memory: 8,
69 | hw: 8,
70 | },
71 | {
72 | name: 'Moto G71 5G',
73 | build: 'moto g71 5G',
74 | viewport: '412x915',
75 | deviceScaleFactor: 2.625,
76 | memory: 4,
77 | hw: 8,
78 | },
79 | {
80 | name: 'OnePlus 9',
81 | build: 'LE2113',
82 | viewport: '384x854',
83 | deviceScaleFactor: 2.8125,
84 | memory: 8,
85 | hw: 8,
86 | },
87 | {
88 | name: 'Samsung Galaxy S22',
89 | build: 'SM-S901U',
90 | viewport: '360x780',
91 | deviceScaleFactor: 3,
92 | memory: 8,
93 | hw: 8,
94 | },
95 | ],
96 | and3: [
97 | {
98 | name: 'Google Pixel 7',
99 | build: 'Pixel 7',
100 | viewport: '412x915',
101 | deviceScaleFactor: 2.625,
102 | memory: 8,
103 | hw: 8,
104 | },
105 | {
106 | name: 'Google Pixel 7 Pro',
107 | build: 'Pixel 7 Pro',
108 | viewport: '412x892',
109 | deviceScaleFactor: 2.625,
110 | memory: 8,
111 | hw: 8,
112 | },
113 | {
114 | name: 'Redmi Note 12 4G',
115 | build: '23027RAD4I',
116 | viewport: '393x783',
117 | deviceScaleFactor: 2.75,
118 | memory: 4,
119 | hw: 8,
120 | },
121 | {
122 | name: 'Samsung Galaxy S23',
123 | build: 'SM-S911B',
124 | viewport: '360x780',
125 | deviceScaleFactor: 3,
126 | memory: 8,
127 | hw: 8,
128 | },
129 | {
130 | name: 'OnePlus 11',
131 | build: 'CPH2487',
132 | viewport: '355x793',
133 | deviceScaleFactor: 3.5,
134 | memory: 8,
135 | hw: 8,
136 | },
137 | ],
138 | and4: [
139 | {
140 | name: 'Google Pixel 8',
141 | build: 'Pixel 8',
142 | viewport: '412x915',
143 | deviceScaleFactor: 2.625,
144 | memory: 8,
145 | hw: 9,
146 | },
147 | {
148 | name: 'Google Pixel 8 Pro',
149 | build: 'Pixel 8 Pro',
150 | viewport: '448x998',
151 | deviceScaleFactor: 2.25,
152 | memory: 8,
153 | hw: 9,
154 | },
155 | {
156 | name: 'OnePlus 11',
157 | build: 'CPH2487',
158 | viewport: '355x793',
159 | deviceScaleFactor: 3.5,
160 | memory: 8,
161 | hw: 8,
162 | },
163 | {
164 | name: 'Samsung Galaxy S23',
165 | build: 'SM-S911B',
166 | viewport: '360x780',
167 | deviceScaleFactor: 3,
168 | memory: 8,
169 | hw: 8,
170 | },
171 | {
172 | name: 'Samsung Galaxy S23 Ultra',
173 | build: 'SM-S918B',
174 | viewport: '384x824',
175 | deviceScaleFactor: 2.8125,
176 | memory: 8,
177 | hw: 8,
178 | },
179 | ],
180 | ios1: [
181 | {
182 | name: 'iPhone 6S',
183 | build: '19H370',
184 | viewport: '375x667',
185 | deviceScaleFactor: 2,
186 | },
187 | {
188 | name: 'iPhone 6S Plus',
189 | build: '19H370',
190 | viewport: '414x736',
191 | deviceScaleFactor: 3,
192 | },
193 | {
194 | name: 'iPhone SE (2022)',
195 | build: '19H370',
196 | viewport: '375x548',
197 | deviceScaleFactor: 2,
198 | },
199 | {
200 | name: 'iPhone 7',
201 | build: '19H370',
202 | viewport: '375x667',
203 | deviceScaleFactor: 2,
204 | },
205 | {
206 | name: 'iPhone 7 Plus',
207 | build: '19H370',
208 | viewport: '414x736',
209 | deviceScaleFactor: 3,
210 | },
211 | {
212 | name: 'iPhone 8',
213 | build: '19H370',
214 | viewport: '375x667',
215 | deviceScaleFactor: 2,
216 | },
217 | {
218 | name: 'iPhone 8 Plus',
219 | build: '19H370',
220 | viewport: '414x736',
221 | deviceScaleFactor: 3,
222 | },
223 | {
224 | name: 'iPhone X',
225 | build: '19H370',
226 | viewport: '375x812',
227 | deviceScaleFactor: 3,
228 | },
229 | {
230 | name: 'iPhone XS',
231 | build: '19H370',
232 | viewport: '375x812',
233 | deviceScaleFactor: 3,
234 | },
235 | {
236 | name: 'iPhone XS Max',
237 | build: '19H370',
238 | viewport: '414x896',
239 | deviceScaleFactor: 3,
240 | },
241 | {
242 | name: 'iPhone XR',
243 | build: '19H370',
244 | viewport: '414x896',
245 | deviceScaleFactor: 3,
246 | },
247 | {
248 | name: 'iPhone 11',
249 | build: '19H370',
250 | viewport: '414x896',
251 | deviceScaleFactor: 3,
252 | },
253 | {
254 | name: 'iPhone 11 Pro',
255 | build: '19H370',
256 | viewport: '375x812',
257 | deviceScaleFactor: 3,
258 | },
259 | {
260 | name: 'iPhone 11 Pro Max',
261 | build: '19H370',
262 | viewport: '414x896',
263 | deviceScaleFactor: 3,
264 | },
265 | {
266 | name: 'iPhone 12',
267 | build: '19H370',
268 | viewport: '360x780',
269 | deviceScaleFactor: 3,
270 | },
271 | {
272 | name: 'iPhone 12 Pro',
273 | build: '19H370',
274 | viewport: '390x844',
275 | deviceScaleFactor: 3,
276 | },
277 | {
278 | name: 'iPhone 12 Pro Max',
279 | build: '19H370',
280 | viewport: '428x926',
281 | deviceScaleFactor: 3,
282 | },
283 | {
284 | name: 'iPhone 13',
285 | build: '19H370',
286 | viewport: '390x844',
287 | deviceScaleFactor: 3,
288 | },
289 | {
290 | name: 'iPhone 13 Pro',
291 | build: '19H370',
292 | viewport: '390x844',
293 | deviceScaleFactor: 3,
294 | },
295 | {
296 | name: 'iPhone 13 Pro Max',
297 | build: '19H370',
298 | viewport: '428x926',
299 | deviceScaleFactor: 3,
300 | },
301 | ],
302 | },
303 | tablet: {
304 | and1: [
305 | {
306 | name: 'Samsung Galaxy Tab S4 10.5',
307 | build: 'SM-T830',
308 | viewport: '800x1280',
309 | deviceScaleFactor: 2,
310 | memory: 4,
311 | hw: 8,
312 | },
313 | {
314 | name: 'Samsung Galaxy Tab A 10.5',
315 | build: 'SM-T590',
316 | viewport: '600x960',
317 | deviceScaleFactor: 2,
318 | memory: 2,
319 | hw: 8,
320 | },
321 | {
322 | name: 'Xiaomi Mi Pad 4',
323 | build: 'MI PAD 4',
324 | viewport: '600x960',
325 | deviceScaleFactor: 2,
326 | memory: 2,
327 | hw: 8,
328 | },
329 | {
330 | name: 'Xiaomi Mi Pad 4 Plus',
331 | build: 'MI PAD 4 PLUS',
332 | viewport: '600x960',
333 | deviceScaleFactor: 2,
334 | memory: 4,
335 | hw: 8,
336 | },
337 | ],
338 | and2: [
339 | {
340 | name: 'Samsung Galaxy Tab S4 10.5',
341 | build: 'SM-T830',
342 | viewport: '800x1280',
343 | deviceScaleFactor: 2,
344 | memory: 4,
345 | hw: 8,
346 | },
347 | {
348 | name: 'ASUS ZenPad 3S 10',
349 | build: 'P027',
350 | viewport: '748x1024',
351 | deviceScaleFactor: 2,
352 | memory: 4,
353 | hw: 8,
354 | },
355 | {
356 | name: 'Samsung Galaxy Tab S5e',
357 | build: 'SM-T720',
358 | viewport: '800x1280',
359 | deviceScaleFactor: 2,
360 | memory: 4,
361 | hw: 8,
362 | },
363 | {
364 | name: 'Samsung Galaxy Tab A 10.1 (2019)',
365 | build: 'SM-T510',
366 | viewport: '600x960',
367 | deviceScaleFactor: 1.5,
368 | memory: 2,
369 | hw: 8,
370 | },
371 | ],
372 | and3: [
373 | {
374 | name: 'Samsung Galaxy Tab S4 10.5',
375 | build: 'SM-T830',
376 | viewport: '800x1280',
377 | deviceScaleFactor: 2,
378 | memory: 4,
379 | hw: 8,
380 | },
381 | {
382 | name: 'Samsung Galaxy Tab S5e',
383 | build: 'SM-T720',
384 | viewport: '800x1280',
385 | deviceScaleFactor: 2,
386 | memory: 4,
387 | hw: 8,
388 | },
389 | {
390 | name: 'Samsung Galaxy Tab A 10.1 (2019)',
391 | build: 'SM-T510',
392 | viewport: '600x960',
393 | deviceScaleFactor: 1.5,
394 | memory: 2,
395 | hw: 8,
396 | },
397 | {
398 | name: 'Samsung Galaxy Tab A7 10.4 (2020)',
399 | build: 'SM-T500',
400 | viewport: '600x1000',
401 | deviceScaleFactor: 2,
402 | memory: 2,
403 | hw: 8,
404 | },
405 | ],
406 | and4: [
407 | {
408 | name: 'Samsung Galaxy Tab S7+',
409 | build: 'SM-T970',
410 | viewport: '876x1400',
411 | deviceScaleFactor: 2,
412 | memory: 4,
413 | hw: 8,
414 | },
415 | {
416 | name: 'Samsung Galaxy Tab S5e',
417 | build: 'SM-T720',
418 | viewport: '800x1280',
419 | deviceScaleFactor: 2,
420 | memory: 4,
421 | hw: 8,
422 | },
423 | {
424 | name: 'Samsung Galaxy Tab A7 10.4 (2020)',
425 | build: 'SM-T500',
426 | viewport: '600x1000',
427 | deviceScaleFactor: 2,
428 | memory: 2,
429 | hw: 8,
430 | },
431 | ],
432 | ios1: [
433 | {
434 | device: 'iPad Air 2',
435 | build: '19H370',
436 | viewport: '768x1024',
437 | deviceScaleFactor: 2,
438 | },
439 | {
440 | device: 'iPad (2017)',
441 | build: '19H370',
442 | viewport: '768x1024',
443 | deviceScaleFactor: 2,
444 | },
445 | {
446 | device: 'iPad (2018)',
447 | build: '19H370',
448 | viewport: '768x1024',
449 | deviceScaleFactor: 2,
450 | },
451 | {
452 | device: 'iPad Mini 4',
453 | build: '19H370',
454 | viewport: '768x1024',
455 | deviceScaleFactor: 2,
456 | },
457 | {
458 | device: 'iPad Pro (9.7in)',
459 | build: '19H370',
460 | viewport: '768x1024',
461 | deviceScaleFactor: 2,
462 | },
463 | {
464 | device: 'iPad Pro (10.5in)',
465 | build: '19H370',
466 | viewport: '834x1112',
467 | deviceScaleFactor: 2,
468 | },
469 | {
470 | device: 'iPad Pro (12.9in, 1st gen)',
471 | build: '19H370',
472 | viewport: '1024x1366',
473 | deviceScaleFactor: 2,
474 | },
475 | ],
476 | },
477 | };
478 |
479 | devices.mobile.ios2 = (devices.mobile.ios1.map((d: Device) => {
480 | return { build: '20H115', name: d.name, viewport: d.viewport };
481 | }) as any).concat([
482 | {
483 | name: 'iPhone 14',
484 | build: '20H115',
485 | viewport: '390x844',
486 | deviceScaleFactor: 3,
487 | },
488 | {
489 | name: 'iPhone 14 Pro',
490 | build: '20H115',
491 | viewport: '393x852',
492 | deviceScaleFactor: 3,
493 | },
494 | {
495 | name: 'iPhone 14 Pro Max',
496 | build: '20H115',
497 | viewport: '430x932',
498 | deviceScaleFactor: 3,
499 | },
500 | ]);
501 |
502 | devices.mobile.ios3 = (devices.mobile.ios2.map((d: Device) => {
503 | return { build: '21B74', name: d.name, viewport: d.viewport };
504 | }) as any).concat([
505 | {
506 | name: 'iPhone 15',
507 | build: '21B80',
508 | viewport: '393x852',
509 | deviceScaleFactor: 3,
510 | },
511 | {
512 | name: 'iPhone 15 Pro',
513 | build: '21B80',
514 | viewport: '393x852',
515 | deviceScaleFactor: 3,
516 | },
517 | {
518 | name: 'iPhone 15 Pro Max',
519 | build: '21B74',
520 | viewport: '430x932',
521 | deviceScaleFactor: 3,
522 | },
523 | ]);
524 |
525 | devices.tablet.ios2 = (devices.tablet.ios1.map((d: Device) => {
526 | return { build: '20H115', name: d.name, viewport: d.viewport, deviceScaleFactor: d.deviceScaleFactor };
527 | }) as any).concat([
528 | {
529 | name: 'iPad Air (2019)',
530 | build: '20H115',
531 | viewport: '834x1112',
532 | deviceScaleFactor: 2,
533 | },
534 | {
535 | name: 'iPad Mini (5th gen)',
536 | build: '20H115',
537 | viewport: '768x1024',
538 | deviceScaleFactor: 2,
539 | },
540 | {
541 | name: 'iPad Pro (12.9-inch 2nd gen)',
542 | build: '20H115',
543 | viewport: '1024x1366',
544 | deviceScaleFactor: 2,
545 | },
546 | {
547 | name: 'iPad Pro (12.9-inch 3rd gen)',
548 | build: '20H115',
549 | viewport: '1024x1366',
550 | deviceScaleFactor: 2,
551 | },
552 | ]);
553 |
554 | devices.tablet.ios3 = devices.tablet.ios2.map((d: Device) => {
555 | return { build: '21B74', name: d.name, viewport: d.viewport, deviceScaleFactor: d.deviceScaleFactor };
556 | }) as any;
557 |
558 | let getDevice = (hardware: string, id: string): Device => {
559 | return devices[hardware][id][Math.floor(Math.random() * devices[hardware][id].length)];
560 | };
561 |
562 | export { getDevice };
563 |
--------------------------------------------------------------------------------
/src/lib/intercept.ts:
--------------------------------------------------------------------------------
1 | // intercepts requests
2 | import * as prof from './profiles';
3 | import * as lang from './language';
4 | import util from './util';
5 | import whitelisted from './whitelisted';
6 |
7 | enum RefererXOriginOption {
8 | AlwaysSend = 0,
9 | MatchBaseDomain = 1,
10 | MatchHost = 2,
11 | }
12 |
13 | enum RefererTrimOption {
14 | SendFullURI = 0,
15 | SchemeWithPath = 1,
16 | SchemeNoPath = 2,
17 | }
18 |
19 | interface WhitelistOptions {
20 | name: boolean;
21 | ref: boolean;
22 | tz: boolean;
23 | ws: boolean;
24 | }
25 |
26 | interface WhitelistResult {
27 | active: boolean;
28 | opt?: WhitelistOptions;
29 | lang?: string;
30 | pattern?: object;
31 | profile?: string;
32 | spoofIP?: string;
33 | }
34 |
35 | class Interceptor {
36 | private LINK: any;
37 | private profiles: prof.Generator;
38 | private profileCache: any;
39 | private settings: any;
40 | private tempStore: any;
41 | private regex: any;
42 | private olderThanNinety: boolean;
43 |
44 | constructor(settings: any, tempStore: any, profileCache: any, olderThanNinety: boolean) {
45 | this.regex = {
46 | CLOUDFLARE: RegExp(/chk_jschl/),
47 | HTTPS: RegExp(/^https:\/\//),
48 | };
49 |
50 | this.LINK = document.createElement('a');
51 | this.profiles = new prof.Generator();
52 | this.settings = settings;
53 | this.tempStore = tempStore;
54 | this.profileCache = profileCache;
55 | this.olderThanNinety = olderThanNinety;
56 | }
57 |
58 | blockWebsocket(details: any): any {
59 | if (!this.settings.config.enabled) return;
60 |
61 | let wl = this.checkWhitelist(details);
62 |
63 | if (!wl.active && this.settings.options.webSockets === 'allow_all') {
64 | return;
65 | }
66 |
67 | let isWebSocketRequest = false;
68 | if (details.requestHeaders) {
69 | for (let h of details.requestHeaders) {
70 | if (h.name.toLowerCase() == 'x-websocket-extensions') {
71 | isWebSocketRequest = true;
72 | }
73 | }
74 | }
75 |
76 | if (details.type === 'websocket' || details.url.includes('transport=polling') || isWebSocketRequest) {
77 | if (wl.active) {
78 | return { cancel: wl.opt.ws };
79 | }
80 |
81 | if (this.settings.options.webSockets === 'block_all') {
82 | return { cancel: true };
83 | } else if (this.settings.options.webSockets === 'block_3rd_party') {
84 | let frame = util.parseURL(details.documentUrl || details.originUrl);
85 | let ws = util.parseURL(details.url);
86 |
87 | if (!frame.error && !ws.error) {
88 | if (frame.domain != ws.domain) {
89 | return { cancel: true };
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
96 | checkWhitelist(request: any): WhitelistResult {
97 | let url: string;
98 |
99 | /* Get document url of request */
100 | if (request.type === 'main_frame') {
101 | url = request.url;
102 | } else if (request.parentFrameId == -1) {
103 | url = request.documentUrl;
104 | } else {
105 | let root = request.frameAncestors ? request.frameAncestors.find(f => f.frameId === 0) : '';
106 | if (root) {
107 | url = root.url;
108 | } else {
109 | url = request.documentUrl;
110 | }
111 | }
112 |
113 | if (url) {
114 | this.LINK.href = url;
115 | let rule = util.findWhitelistRule(this.settings.whitelist.rules, this.LINK.host, url);
116 |
117 | if (rule) {
118 | return {
119 | active: true,
120 | lang: rule.lang,
121 | opt: rule.options,
122 | pattern: rule.pattern,
123 | profile: rule.profile,
124 | spoofIP: rule.spoofIP,
125 | };
126 | }
127 | }
128 |
129 | return { active: false };
130 | }
131 |
132 | modifyRequest(details: any): any {
133 | if (!this.settings.config.enabled) return;
134 |
135 | // don't modify request for sites below
136 | for (let i = 0; i < whitelisted.length; i++) {
137 | if (
138 | (details.originUrl && details.originUrl.startsWith(whitelisted[i])) ||
139 | (details.documentUrl && details.documentUrl.startsWith(whitelisted[i])) ||
140 | (details.url && details.url.startsWith(whitelisted[i]))
141 | ) {
142 | return;
143 | }
144 | }
145 |
146 | this.LINK.href = details.documentUrl || details.url;
147 | if (util.isInternalIP(this.LINK.hostname)) return;
148 |
149 | let wl: WhitelistResult = this.checkWhitelist(details);
150 |
151 | // used to send different accept headers for https requests
152 | let isSecure: boolean = this.regex.HTTPS.test(details.url);
153 | let dntIndex: number = -1;
154 |
155 | let profile: prof.BrowserProfile;
156 | if (wl.active) {
157 | if (wl.profile === 'default') {
158 | if (this.settings.whitelist.defaultProfile != 'none') {
159 | profile = this.profileCache[this.settings.whitelist.defaultProfile];
160 | }
161 | } else if (wl.profile != 'none') {
162 | profile = this.profileCache[wl.profile];
163 | }
164 | } else {
165 | if (this.settings.profile.selected != 'none' && !this.settings.excluded.includes(this.settings.profile.selected) && this.tempStore.profile != 'none') {
166 | let profileUsed: string = this.settings.profile.selected.includes('-') ? this.settings.profile.selected : this.tempStore.profile;
167 | profile = this.profileCache[profileUsed];
168 | }
169 | }
170 |
171 | let isChromeBased: boolean = profile ? profile.navigator.userAgent.includes('Chrome') : false;
172 |
173 | for (let i = 0; i < details.requestHeaders.length; i++) {
174 | let header: string = details.requestHeaders[i].name.toLowerCase();
175 |
176 | if (header === 'referer') {
177 | if (!wl.active) {
178 | if (this.settings.headers.referer.disabled) {
179 | details.requestHeaders[i].value = '';
180 | } else {
181 | // check referer policies, preserve referer for cloudflare challenges
182 | if (!this.regex.CLOUDFLARE.test(details.url)) {
183 | if (this.settings.headers.referer.xorigin != RefererXOriginOption.AlwaysSend) {
184 | let dest = util.parseURL(details.url);
185 | let ref = util.parseURL(details.requestHeaders[i].value);
186 |
187 | if (this.settings.headers.referer.xorigin === RefererXOriginOption.MatchBaseDomain) {
188 | if (dest.domain != ref.domain) {
189 | details.requestHeaders[i].value = '';
190 | }
191 | } else {
192 | if (dest.origin != ref.origin) {
193 | details.requestHeaders[i].value = '';
194 | }
195 | }
196 | }
197 |
198 | if (this.settings.headers.referer.trimming != RefererTrimOption.SendFullURI) {
199 | if (details.requestHeaders[i].value != '') {
200 | let ref = util.parseURL(details.requestHeaders[i].value);
201 | details.requestHeaders[i].value = this.settings.headers.referer.trimming === RefererTrimOption.SchemeWithPath ? ref.origin + ref.pathname : ref.origin;
202 | }
203 | }
204 | }
205 | }
206 | } else if (wl.opt.ref) {
207 | details.requestHeaders[i].value = '';
208 | }
209 | } else if (header === 'user-agent') {
210 | if (profile) {
211 | details.requestHeaders[i].value = profile.navigator.userAgent;
212 | }
213 | } else if (header === 'accept') {
214 | if (details.type === 'main_frame' || details.type === 'sub_frame') {
215 | if (profile) {
216 | details.requestHeaders[i].value = profile.accept.header;
217 | }
218 | }
219 | } else if (header === 'accept-encoding') {
220 | if (details.type === 'main_frame' || details.type === 'sub_frame') {
221 | if (profile) {
222 | details.requestHeaders[i].value = isSecure ? profile.accept.encodingHTTPS : profile.accept.encodingHTTP;
223 | }
224 | }
225 | } else if (header === 'accept-language') {
226 | if (wl.active && wl.lang != '') {
227 | details.requestHeaders[i].value = wl.lang;
228 | } else {
229 | if (this.settings.headers.spoofAcceptLang.enabled) {
230 | if (this.settings.headers.spoofAcceptLang.value === 'ip') {
231 | if (this.tempStore.ipInfo.lang) {
232 | details.requestHeaders[i].value = lang.getLanguage(this.tempStore.ipInfo.lang).value;
233 | }
234 | } else if (this.settings.headers.spoofAcceptLang.value !== 'default') {
235 | details.requestHeaders[i].value = lang.getLanguage(this.settings.headers.spoofAcceptLang.value).value;
236 | }
237 | }
238 | }
239 | } else if (header === 'dnt') {
240 | dntIndex = i;
241 | }
242 | }
243 |
244 | if (this.settings.headers.enableDNT) {
245 | if (dntIndex === -1) {
246 | details.requestHeaders.push({ name: 'DNT', value: '1' });
247 | }
248 | } else {
249 | if (dntIndex > -1) {
250 | details.requestHeaders.splice(dntIndex, 1);
251 | }
252 | }
253 |
254 | if (wl.active) {
255 | if (wl.spoofIP) {
256 | if (
257 | // don't spoof header IP for cloudflare pages
258 | !details.url.includes('cdn-cgi/challenge-platform/generate/') &&
259 | !details.url.includes('__cf_chl_jschl_tk__=') &&
260 | !details.url.includes('jschal/js/nocookie/transparent.gif')
261 | ) {
262 | details.requestHeaders.push({
263 | name: 'Via',
264 | value: '1.1 ' + wl.spoofIP,
265 | });
266 | details.requestHeaders.push({
267 | name: 'X-Forwarded-For',
268 | value: wl.spoofIP,
269 | });
270 | }
271 | }
272 | } else {
273 | if (this.settings.headers.spoofIP.enabled) {
274 | if (
275 | // don't spoof header IP for cloudflare pages
276 | !details.url.includes('cdn-cgi/challenge-platform/generate/') &&
277 | !details.url.includes('__cf_chl_jschl_tk__=') &&
278 | !details.url.includes('jschal/js/nocookie/transparent.gif')
279 | ) {
280 | details.requestHeaders.push({
281 | name: 'Via',
282 | value: '1.1 ' + this.tempStore.spoofIP,
283 | });
284 | details.requestHeaders.push({
285 | name: 'X-Forwarded-For',
286 | value: this.tempStore.spoofIP,
287 | });
288 | }
289 | }
290 | }
291 |
292 | if (isSecure && isChromeBased && this.olderThanNinety) {
293 | // https://www.w3.org/TR/fetch-metadata/#sec-fetch-dest-header
294 | // implementation below is missing some destinations (mostly worker related)
295 | let secDest = 'empty';
296 |
297 | if (details.type == 'main_frame') {
298 | secDest = 'document';
299 | } else if (details.type == 'sub_frame') {
300 | secDest = 'iframe';
301 | } else if (details.type == 'font') {
302 | secDest = 'font';
303 | } else if (details.type == 'imageset' || details.type == 'image') {
304 | secDest = 'image';
305 | } else if (details.type == 'media') {
306 | let h = details.requestHeaders.find(r => r.name.toLowerCase() == 'accept');
307 | if (h.value.charAt(0) == 'a') {
308 | secDest = 'audio';
309 | } else if (h.value.charAt(0) == 'v') {
310 | secDest = 'video';
311 | } else if (details.url.includes('.vtt')) {
312 | secDest = 'track';
313 | }
314 | } else if (details.type == 'xslt') {
315 | secDest = 'xslt';
316 | } else if (details.type == 'web_manifest') {
317 | secDest = 'manifest';
318 | } else if (details.type == 'csp_report') {
319 | secDest = 'report';
320 | } else if (details.type == 'object') {
321 | secDest = 'object'; // object is used for both