tor push
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
.idea
|
||||
*.iml
|
||||
dist
|
||||
web-ext-artifacts
|
||||
source-artifacts
|
||||
node_modules
|
||||
tmpProfile
|
||||
*.module.scss.d.ts
|
||||
@@ -0,0 +1,25 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2019, Bekh-Ivanov Aleksey
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Notes to Reviewer
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[](https://gitter.im/firefox-container-proxy/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://circleci.com/gh/bekh6ex/firefox-container-proxy/tree/master)
|
||||
[](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||
|
||||
|
||||
[Extension page](https://addons.mozilla.org/en-US/firefox/addon/container-proxy/)
|
||||
|
||||
## Permissions
|
||||
|
||||
* **cookies**: needed to identify to which container request belongs
|
||||
* **webRequest** and **webRequestBlocking**: to supply credentials for proxy authorization (but not for normal web authorization)
|
||||
|
||||
## Good to know
|
||||
|
||||
There is a known issue with DNS leak happening in non-default containers when uBlock is installed. The issue is not resolvable by this extension, but can be resolved adjusting in uBlock settings. See [comment](https://github.com/bekh6ex/firefox-container-proxy/issues/23#issuecomment-773249909)
|
||||
|
||||
## Translation
|
||||
[](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||
|
||||
[Translate to your language](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// We need to tell TypeScript that when we write "import styles from './styles.scss' we mean to load a module (to look for a './styles.scss.d.ts').
|
||||
declare module '*.module.scss';
|
||||
+17856
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"name": "container-proxy",
|
||||
"version": "0.1.0",
|
||||
"description": "[Firefox extension] Assign a proxy to a Firefox container [Extension page](https://addons.mozilla.org/en-US/firefox/addon/container-proxy/)",
|
||||
"scripts": {
|
||||
"test": "pnpm run test:unit",
|
||||
"bundle": "web-ext -s dist build --overwrite-dest",
|
||||
"bundle:src": "V=$(cat src/manifest.json | jq -r .version) && zip -r -X source-artifacts/container-proxy-v${V}.zip src babel.config.js LICENSE package.json package-lock.json README.md tsconfig.json webpack.config.js",
|
||||
"test:func": "pnpm run build && mocha test/functional/",
|
||||
"test:unit": "ts-mocha --require ts-node/register test/unit/**/*.test.ts",
|
||||
"codeStyle:check": "standardx && ts-standard",
|
||||
"codeStyle:fix": "standardx --fix && ts-standard --fix",
|
||||
"lint": "addons-linter dist",
|
||||
"debug": "web-ext run -s dist --verbose --browser-console --keep-profile-changes -p ./tmpProfile --start-url about:debugging --start-url about:addons --pref=intl.locale.requested=en #--pref=intl.locale.matchOS=false",
|
||||
"ci:test-unit": "pnpm run test:unit",
|
||||
"ci:test-func": "HEADLESS=true pnpm run test:func",
|
||||
"ci:style-check": "pnpm run codeStyle:check",
|
||||
"ci:lint": "pnpm run build && pnpm run lint",
|
||||
"build": "webpack",
|
||||
"build:watch": "webpack --watch",
|
||||
"clean": "rm -rf ./dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/bekh6ex/firefox-container-proxy.git"
|
||||
},
|
||||
"author": "Aleksei Bekh-Ivanov",
|
||||
"license": "BSD-2-Clause",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bekh6ex/firefox-container-proxy/issues"
|
||||
},
|
||||
"homepage": "https://addons.mozilla.org/en-US/firefox/addon/container-proxy/",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^5.0.1",
|
||||
"@types/firefox-webext-browser": "^120.0.4",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"addons-linter": "^7.8.0",
|
||||
"chai": "^5.1.2",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-modules-typescript-loader": "^4.0.1",
|
||||
"esm": "^3.2.25",
|
||||
"mocha": "^10.8.2",
|
||||
"sass-loader": "^16.0.4",
|
||||
"sinon": "^8.1.1",
|
||||
"standardx": "^7.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"ts-standard": "^12.0.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"web-ext": "^8.4.0",
|
||||
"webextensions-api-fake": "^1.3.0",
|
||||
"webextensions-geckodriver": "^0.7.0",
|
||||
"webpack": "^5.59.1",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"standardx": {
|
||||
"cache": false,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
},
|
||||
"globals": [
|
||||
"browser",
|
||||
"expect",
|
||||
"store"
|
||||
],
|
||||
"ignore": [
|
||||
"dist",
|
||||
"/src/lib/*",
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
]
|
||||
},
|
||||
"ts-standard": {
|
||||
"ignore": [
|
||||
"dist",
|
||||
"src/**/*.js",
|
||||
"test/**/*.js",
|
||||
"src/options/import/FoxyProxyConverter.ts",
|
||||
"test/unit/options/import/FoxyProxyConverter.test.ts"
|
||||
],
|
||||
"cache": false,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
},
|
||||
"globals": [
|
||||
"browser",
|
||||
"expect",
|
||||
"store"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"rules": {
|
||||
"no-unused-expressions": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.test.js"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-expressions": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
import { Store } from '../store/Store'
|
||||
import { HttpProxySettings, HttpsProxySettings, ProxySettings } from '../domain/ProxySettings'
|
||||
import { ProxyInfo, Socks5ProxyInfo } from '../domain/ProxyInfo'
|
||||
import { ProxyType } from '../domain/ProxyType'
|
||||
import BlockingResponse = browser.webRequest.BlockingResponse
|
||||
import _OnAuthRequiredDetails = browser.webRequest._OnAuthRequiredDetails
|
||||
import _OnRequestDetails = browser.proxy._OnRequestDetails
|
||||
|
||||
const localhosts = new Set(['localhost', '127.0.0.1', '[::1]'])
|
||||
|
||||
const containerIdentifier = 'firefox-container-'
|
||||
|
||||
type DoNotProxy = never[]
|
||||
export const doNotProxy: DoNotProxy = []
|
||||
|
||||
const emergencyBreak: Socks5ProxyInfo = {
|
||||
type: ProxyType.Socks5,
|
||||
host: 'emergency-break-proxy.localhost',
|
||||
port: 1,
|
||||
failoverTimeout: 1,
|
||||
username: 'nonexistent user',
|
||||
password: 'dummy password',
|
||||
proxyDNS: true
|
||||
}
|
||||
|
||||
export default class BackgroundMain {
|
||||
store: Store
|
||||
|
||||
constructor({ store }: { store: Store }) {
|
||||
this.store = store
|
||||
}
|
||||
|
||||
initializeAuthListener(cookieStoreId: string, proxy: HttpProxySettings | HttpsProxySettings): void {
|
||||
const listener: (details: _OnAuthRequiredDetails) => BlockingResponse = (details) => {
|
||||
if (!details.isProxy) return {}
|
||||
|
||||
if (details.cookieStoreId !== cookieStoreId) return {}
|
||||
|
||||
// TODO: Fix in @types/firefox-webext-browser
|
||||
// @ts-expect-error
|
||||
const info = details.proxyInfo
|
||||
if (info.host !== proxy.host || info.port !== proxy.port || info.type !== proxy.type) return {}
|
||||
|
||||
const result = { authCredentials: { username: proxy.username, password: proxy.password } }
|
||||
|
||||
browser.webRequest.onAuthRequired.removeListener(listener)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
browser.webRequest.onAuthRequired.addListener(
|
||||
listener,
|
||||
{ urls: ['<all_urls>'] },
|
||||
['blocking']
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Fix in @types/firefox-webext-browser
|
||||
async onRequest(requestDetails: Pick<_OnRequestDetails, 'cookieStoreId' | 'url' | 'tabId'>): Promise<DoNotProxy | ProxyInfo[]> {
|
||||
const tab = (await browser.tabs.get(requestDetails.tabId))
|
||||
|
||||
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
|
||||
try {
|
||||
const cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
|
||||
|
||||
const proxies = await this.store.getProxiesForContainer(cookieStoreId)
|
||||
|
||||
if (proxies.length > 0) {
|
||||
proxies.forEach(p => {
|
||||
if (p.type === ProxyType.Http || p.type === ProxyType.Https) {
|
||||
this.initializeAuthListener(cookieStoreId, p)
|
||||
}
|
||||
})
|
||||
|
||||
const result: ProxyInfo[] = proxies.filter((p: ProxySettings) => {
|
||||
try {
|
||||
const documentUrl = new URL(requestDetails.url)
|
||||
const isLocalhost = localhosts.has(documentUrl.hostname)
|
||||
if (isLocalhost && p.doNotProxyLocal) {
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
return true
|
||||
}).map(p => p.asProxyInfo())
|
||||
|
||||
if (result.length === 0) {
|
||||
return [emergencyBreak]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return [emergencyBreak]
|
||||
} catch (e: unknown) {
|
||||
console.error(`Error in onRequest listener: ${e as string}`)
|
||||
return [emergencyBreak]
|
||||
}
|
||||
}
|
||||
|
||||
return doNotProxy
|
||||
}
|
||||
|
||||
run(browser: { proxy: any }): void {
|
||||
const filter = { urls: ['<all_urls>'] }
|
||||
|
||||
browser.proxy.onRequest.addListener(this.onRequest.bind(this), filter)
|
||||
|
||||
browser.proxy.onError.addListener((e: Error) => {
|
||||
console.error('Proxy error', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { Socks5ProxySettings } from 'src/domain/ProxySettings';
|
||||
import { Store } from '../store/Store'
|
||||
import BackgroundMain from './BackgroundMain'
|
||||
|
||||
console.log('Background script started')
|
||||
|
||||
const store = new Store()
|
||||
|
||||
interface Message {
|
||||
action: 'setProxyPort' | 'addContainerProxy' | 'removeContainerProxy';
|
||||
args: any;
|
||||
}
|
||||
|
||||
const port = browser.runtime.connectNative("containerProxy");
|
||||
port.onMessage.addListener((raw: unknown): void => {
|
||||
const message = raw as Message;
|
||||
switch (message.action) {
|
||||
case "setProxyPort":
|
||||
store.putProxy(new Socks5ProxySettings({
|
||||
id: 'tor',
|
||||
type: 'socks',
|
||||
host: '127.0.0.1',
|
||||
port: message.args,
|
||||
doNotProxyLocal: true,
|
||||
title: 'Tor',
|
||||
proxyDNS: true,
|
||||
}))
|
||||
console.log('put tor port ' + message.args)
|
||||
|
||||
break
|
||||
case "addContainerProxy":
|
||||
store.setContainerProxyRelation(message.args, "tor")
|
||||
console.log('added container relation ' + message.args)
|
||||
break
|
||||
case "removeContainerProxy":
|
||||
store.removeContainerProxyRelation(message.args, "tor")
|
||||
break
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
const backgroundListener = new BackgroundMain({ store })
|
||||
backgroundListener.run(browser)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { ProxyType } from './ProxyType'
|
||||
|
||||
export type ProxyInfo = Socks4ProxyInfo | Socks5ProxyInfo | HttpProxyInfo | HttpsProxyInfo
|
||||
|
||||
interface ProxyInfoBase<TY extends ProxyType> {
|
||||
type: TY
|
||||
host: string
|
||||
port: number
|
||||
failoverTimeout: number
|
||||
}
|
||||
|
||||
export interface Socks5ProxyInfo extends ProxyInfoBase<ProxyType.Socks5> {
|
||||
type: ProxyType.Socks5
|
||||
username: string
|
||||
password: string
|
||||
proxyDNS: boolean
|
||||
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||
}
|
||||
|
||||
export interface Socks4ProxyInfo extends ProxyInfoBase<ProxyType.Socks4> {
|
||||
type: ProxyType.Socks4
|
||||
proxyDNS: boolean
|
||||
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||
}
|
||||
|
||||
export interface HttpsProxyInfo extends ProxyInfoBase<ProxyType.Https> {
|
||||
type: ProxyType.Https
|
||||
proxyDNS?: undefined
|
||||
proxyAuthorizationHeader: string
|
||||
}
|
||||
|
||||
export interface HttpProxyInfo extends ProxyInfoBase<ProxyType.Http> {
|
||||
type: ProxyType.Http
|
||||
proxyDNS?: undefined
|
||||
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import { ProxyType } from './ProxyType'
|
||||
import { ProxyDao } from '../store/Store'
|
||||
import { HttpProxyInfo, HttpsProxyInfo, ProxyInfo, Socks4ProxyInfo, Socks5ProxyInfo } from './ProxyInfo'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-redeclare */
|
||||
|
||||
export type ProxySettings = Socks4ProxySettings | Socks5ProxySettings | HttpProxySettings | HttpsProxySettings
|
||||
|
||||
const failoverTimeout = 5
|
||||
|
||||
export namespace ProxySettings {
|
||||
|
||||
export function tryFromDao(dao: ProxyDao): ProxySettings | undefined {
|
||||
switch (dao.type) {
|
||||
case 'socks':
|
||||
return new Socks5ProxySettings(dao)
|
||||
case 'socks4':
|
||||
return new Socks4ProxySettings(dao)
|
||||
case 'http':
|
||||
return new HttpProxySettings(dao)
|
||||
case 'https':
|
||||
return new HttpsProxySettings(dao)
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ProxySettingsBase<PI extends ProxyInfo> {
|
||||
readonly id: string
|
||||
readonly title: string
|
||||
readonly host: string
|
||||
readonly port: number
|
||||
readonly doNotProxyLocal: boolean
|
||||
|
||||
protected constructor(dao: ProxyDao) {
|
||||
this.id = dao.id
|
||||
this.title = dao.title
|
||||
this.host = dao.host
|
||||
this.port = dao.port
|
||||
this.doNotProxyLocal = dao.doNotProxyLocal
|
||||
}
|
||||
|
||||
abstract get type(): PI['type']
|
||||
|
||||
abstract asProxyInfo(): PI
|
||||
|
||||
abstract asDao(): ProxyDao
|
||||
|
||||
get url(): string {
|
||||
return `${this.type}://${this.host}:${this.port}`
|
||||
}
|
||||
|
||||
protected baseDao(): ProxyDao {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
type: this.type,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
doNotProxyLocal: this.doNotProxyLocal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Socks5ProxySettings extends ProxySettingsBase<Socks5ProxyInfo> {
|
||||
readonly username?: string
|
||||
readonly password?: string
|
||||
readonly proxyDNS: boolean
|
||||
|
||||
constructor(dao: ProxyDao) {
|
||||
super(dao)
|
||||
this.username = dao.username
|
||||
this.password = dao.password
|
||||
this.proxyDNS = dao.proxyDNS ?? true
|
||||
}
|
||||
|
||||
get type(): ProxyType.Socks5 {
|
||||
return ProxyType.Socks5
|
||||
}
|
||||
|
||||
asProxyInfo(): Socks5ProxyInfo {
|
||||
return {
|
||||
type: this.type,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
username: this.username ?? '',
|
||||
password: this.password ?? '',
|
||||
proxyDNS: this.proxyDNS,
|
||||
failoverTimeout
|
||||
}
|
||||
}
|
||||
|
||||
asDao(): ProxyDao {
|
||||
return { ...super.baseDao(), username: this.username, password: this.password, proxyDNS: this.proxyDNS }
|
||||
}
|
||||
}
|
||||
|
||||
export class Socks4ProxySettings extends ProxySettingsBase<Socks4ProxyInfo> {
|
||||
readonly proxyDNS: boolean
|
||||
|
||||
constructor(dao: ProxyDao) {
|
||||
super(dao)
|
||||
this.proxyDNS = dao.proxyDNS ?? true
|
||||
}
|
||||
|
||||
get type(): ProxyType.Socks4 {
|
||||
return ProxyType.Socks4
|
||||
}
|
||||
|
||||
asProxyInfo(): Socks4ProxyInfo {
|
||||
return {
|
||||
type: this.type,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
proxyDNS: this.proxyDNS,
|
||||
failoverTimeout
|
||||
}
|
||||
}
|
||||
|
||||
asDao(): ProxyDao {
|
||||
return { ...super.baseDao(), proxyDNS: this.proxyDNS }
|
||||
}
|
||||
}
|
||||
|
||||
abstract class HttpBasedProxySettings<PI extends (HttpsProxyInfo | HttpProxyInfo)> extends ProxySettingsBase<PI> {
|
||||
readonly username?: string
|
||||
readonly password?: string
|
||||
|
||||
constructor(dao: ProxyDao) {
|
||||
super(dao)
|
||||
this.username = dao.username
|
||||
this.password = dao.password
|
||||
}
|
||||
|
||||
asDao(): ProxyDao {
|
||||
return { ...super.baseDao(), username: this.username, password: this.password }
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpsProxySettings extends HttpBasedProxySettings<HttpsProxyInfo> {
|
||||
get type(): ProxyType.Https {
|
||||
return ProxyType.Https
|
||||
}
|
||||
|
||||
asProxyInfo(): HttpsProxyInfo {
|
||||
return {
|
||||
type: this.type,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
proxyAuthorizationHeader: '', //generateAuthorizationHeader(this.username ?? '', this.password ?? ''),
|
||||
failoverTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpProxySettings extends HttpBasedProxySettings<HttpProxyInfo> {
|
||||
get type(): ProxyType.Http {
|
||||
return ProxyType.Http
|
||||
}
|
||||
|
||||
asProxyInfo(): HttpProxyInfo {
|
||||
return {
|
||||
type: this.type,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
failoverTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-namespace,no-redeclare,import/export */
|
||||
|
||||
export enum ProxyType {
|
||||
Socks5 = 'socks',
|
||||
Socks4 = 'socks4',
|
||||
Http = 'http',
|
||||
Https = 'https'
|
||||
}
|
||||
|
||||
export namespace ProxyType {
|
||||
export function tryFromString(s: string): ProxyType | undefined {
|
||||
switch (s) {
|
||||
case 'socks':
|
||||
return ProxyType.Socks5
|
||||
case 'socks4':
|
||||
return ProxyType.Socks4
|
||||
case 'http':
|
||||
return ProxyType.Http
|
||||
case 'https':
|
||||
return ProxyType.Https
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Container proxy",
|
||||
"version": "0.1.22",
|
||||
"description": "Container Proxy",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "container-proxy@lensai.eu"
|
||||
}
|
||||
},
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"geckoViewAddons",
|
||||
"nativeMessaging",
|
||||
"nativeMessagingFromContent",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"storage",
|
||||
"contextualIdentities",
|
||||
"cookies",
|
||||
"proxy"
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
"background/index.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { ProxyInfo } from '../domain/ProxyInfo'
|
||||
import { ProxyType } from '../domain/ProxyType'
|
||||
import { ProxySettings } from '../domain/ProxySettings'
|
||||
const tryFromDao = ProxySettings.tryFromDao
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace,no-redeclare,import/export */
|
||||
|
||||
export interface ProxyDao {
|
||||
id: string
|
||||
title: string
|
||||
type: string
|
||||
host: string
|
||||
port: number
|
||||
username?: string
|
||||
password?: string
|
||||
proxyDNS?: boolean
|
||||
doNotProxyLocal: boolean
|
||||
}
|
||||
|
||||
export namespace ProxyDao {
|
||||
export function toProxyInfo(proxy: Pick<ProxyDao, 'type' | 'host' | 'port' | 'username' | 'password' | 'proxyDNS'>): ProxyInfo | undefined {
|
||||
const type = ProxyType.tryFromString(proxy.type)
|
||||
if (type === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const base = {
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
failoverTimeout: 5
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ProxyType.Socks5:
|
||||
return {
|
||||
type,
|
||||
...base,
|
||||
username: proxy.username ?? '',
|
||||
password: proxy.password ?? '',
|
||||
proxyDNS: proxy.proxyDNS ?? true
|
||||
}
|
||||
case ProxyType.Socks4:
|
||||
return {
|
||||
type,
|
||||
...base,
|
||||
proxyDNS: proxy.proxyDNS ?? true
|
||||
}
|
||||
case ProxyType.Http:
|
||||
return {
|
||||
type,
|
||||
...base
|
||||
}
|
||||
case ProxyType.Https:
|
||||
return {
|
||||
type,
|
||||
...base,
|
||||
proxyAuthorizationHeader: '' //generateAuthorizationHeader(proxy.username ?? '', proxy.password ?? '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Store {
|
||||
private proxies: ProxyDao[] = []
|
||||
private relations: { [key: string]: string[] } = {}
|
||||
|
||||
async getAllProxies(): Promise<ProxySettings[]> {
|
||||
const proxyDaos = await this.getAllProxyDaos()
|
||||
const result: ProxySettings[] = proxyDaos.map(tryFromDao).filter(p => p !== undefined) as ProxySettings[]
|
||||
return result
|
||||
}
|
||||
|
||||
async getProxyById(id: string): Promise<ProxySettings | null> {
|
||||
const proxies = await this.getAllProxies()
|
||||
const index = proxies.findIndex(p => p.id === id)
|
||||
if (index === -1) {
|
||||
return null
|
||||
} else {
|
||||
return proxies[index]
|
||||
}
|
||||
}
|
||||
|
||||
async putProxy(proxy: ProxySettings): Promise<void> {
|
||||
const proxies = await this.getAllProxyDaos()
|
||||
const index = proxies.findIndex(p => p.id === proxy.id)
|
||||
if (index !== -1) {
|
||||
proxies[index] = proxy.asDao()
|
||||
} else {
|
||||
proxies.push(proxy.asDao())
|
||||
}
|
||||
|
||||
await this.saveProxyDaos(proxies)
|
||||
}
|
||||
|
||||
async deleteProxyById(id: string): Promise<void> {
|
||||
const proxies = await this.getAllProxyDaos()
|
||||
const index = proxies.findIndex(p => p.id === id)
|
||||
if (index !== -1) {
|
||||
proxies.splice(index, 1)
|
||||
await this.saveProxyDaos(proxies)
|
||||
}
|
||||
}
|
||||
|
||||
async getRelations(): Promise<{ [key: string]: string[] }> {
|
||||
return this.relations
|
||||
}
|
||||
|
||||
async setContainerProxyRelation(cookieStoreId: string, proxyId: string): Promise<void> {
|
||||
this.relations[cookieStoreId] = [proxyId]
|
||||
}
|
||||
|
||||
async removeContainerProxyRelation(cookieStoreId: string, proxyId: string): Promise<void> {
|
||||
const currentRelations = this.relations[cookieStoreId] ?? []
|
||||
this.relations[cookieStoreId] = currentRelations.filter(id => id !== proxyId)
|
||||
|
||||
// If no relations left for this container, clean up by removing the key
|
||||
if (this.relations[cookieStoreId].length === 0) {
|
||||
delete this.relations[cookieStoreId]
|
||||
}
|
||||
}
|
||||
|
||||
async getProxiesForContainer(cookieStoreId: string): Promise<ProxySettings[]> {
|
||||
const relations = await this.getRelations()
|
||||
|
||||
const proxyIds: string[] = relations[cookieStoreId] ?? []
|
||||
|
||||
if (proxyIds.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const proxies = await this.getAllProxies()
|
||||
const proxyById: { [key: string]: ProxySettings } = {}
|
||||
proxies.forEach(function (p) { proxyById[p.id] = p })
|
||||
|
||||
return proxyIds.map(pId => proxyById[pId])
|
||||
.filter(p => p !== undefined)
|
||||
.map(fillInDefaults)
|
||||
.map(tryFromDao)
|
||||
.filter(p => p !== undefined) as ProxySettings[]
|
||||
}
|
||||
|
||||
private async saveProxyDaos(p: ProxyDao[]): Promise<void> {
|
||||
this.proxies = p
|
||||
}
|
||||
|
||||
private async getAllProxyDaos(): Promise<ProxyDao[]> {
|
||||
return this.proxies.map(fillInDefaults)
|
||||
}
|
||||
}
|
||||
|
||||
function fillInDefaults(proxy: Partial<ProxyDao>): ProxyDao {
|
||||
if (proxy.title === undefined) {
|
||||
proxy.title = ''
|
||||
}
|
||||
if (typeof proxy.doNotProxyLocal === 'undefined') {
|
||||
proxy.doNotProxyLocal = true
|
||||
}
|
||||
if (typeof proxy.proxyDNS === 'undefined') {
|
||||
if (proxy.type === 'socks' || proxy.type === 'socks4') {
|
||||
proxy.proxyDNS = true
|
||||
}
|
||||
}
|
||||
return proxy as ProxyDao
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
const PageObject = require('./page-objects/PageObject.js')
|
||||
const OptionsPageObject = require('./page-objects/OptionsPageObject.js')
|
||||
|
||||
const path = require('path')
|
||||
const assert = require('chai')
|
||||
const expect = assert.expect
|
||||
|
||||
const webExtensionsGeckoDriver = require('webextensions-geckodriver')
|
||||
const { webdriver, firefox } = webExtensionsGeckoDriver
|
||||
const { until, By } = webdriver
|
||||
|
||||
const manifestPath = path.resolve(path.join(__dirname, '../../dist/manifest.json'))
|
||||
|
||||
describe('Container Proxy extension', function () {
|
||||
let geckodriver
|
||||
this.timeout(30000)
|
||||
|
||||
before(async () => {
|
||||
const fxOptions = new firefox.Options()
|
||||
if (process.env.HEADLESS) {
|
||||
fxOptions.headless()
|
||||
.windowSize({ height: 1080, width: 1920 })
|
||||
}
|
||||
|
||||
const webExtension = await webExtensionsGeckoDriver(manifestPath, { fxOptions })
|
||||
geckodriver = webExtension.geckodriver
|
||||
})
|
||||
|
||||
it('should add a proxy', async () => {
|
||||
const helper = new Helper(geckodriver)
|
||||
|
||||
const options = await helper.openOptionsPage()
|
||||
|
||||
let proxyList = await options.openProxyList()
|
||||
|
||||
const proxyForm = await proxyList.openAddProxyForm()
|
||||
|
||||
await proxyForm.selectProtocol('socks')
|
||||
await proxyForm.typeInServer('localhost')
|
||||
await proxyForm.typeInPort(1080)
|
||||
await proxyForm.typeInUsername('user')
|
||||
await proxyForm.typeInPassword('password')
|
||||
|
||||
await proxyForm.testSettings()
|
||||
|
||||
proxyList = await proxyForm.saveSettings()
|
||||
|
||||
const proxyLabel = 'socks://localhost:1080'
|
||||
await geckodriver.wait(async () => {
|
||||
const row = await geckodriver.wait(until.elementLocated(
|
||||
By.css('.proxy-list-item:first-of-type')
|
||||
), 2000)
|
||||
|
||||
const label = row.findElement(By.css('.proxy-name'))
|
||||
|
||||
const text = await label.getText()
|
||||
return text === proxyLabel
|
||||
}, 1000, 'Should show proxy in the list')
|
||||
|
||||
const assign = await options.openAssignProxy()
|
||||
const defaultContainerSelect = await assign.defaultContainerSelect()
|
||||
await defaultContainerSelect.selectByLabel(proxyLabel)
|
||||
})
|
||||
|
||||
it.skip('should contain IP address text', async () => {
|
||||
await geckodriver.setContext(firefox.Context.CONTENT)
|
||||
await geckodriver.get('https://api.duckduckgo.com/?q=ip&no_html=1&format=json&t=firefox-container-proxy-extension')
|
||||
const text = await geckodriver.getPageSource()
|
||||
|
||||
expect(text).to.include('Your IP address is')
|
||||
})
|
||||
|
||||
it('should successfully use SOCKS5 proxy for default container', async () => {
|
||||
const helper = new Helper(geckodriver)
|
||||
|
||||
const optionsPage = await helper.openOptionsPage()
|
||||
const proxyList = await optionsPage.openProxyList()
|
||||
const addProxyForm = await proxyList.openAddProxyForm()
|
||||
const title = 'Valid SOCKS5 proxy'
|
||||
await addProxyForm.addProxy({
|
||||
title: title,
|
||||
type: 'socks',
|
||||
server: 'localhost',
|
||||
port: 1080,
|
||||
username: 'user',
|
||||
password: 'password'
|
||||
})
|
||||
// TODO: Check if username and password are actually verified by "dante"
|
||||
|
||||
const assignProxy = await optionsPage.openAssignProxy()
|
||||
await assignProxy.selectForDefaultContainer(title)
|
||||
await helper.assertCanGetTheIpAddress()
|
||||
})
|
||||
|
||||
it('should fail with incorrect SOCKS5 proxy settings', async () => {
|
||||
const helper = new Helper(geckodriver)
|
||||
|
||||
const optionsPage = await helper.openOptionsPage()
|
||||
const proxyList = await optionsPage.openProxyList()
|
||||
const addProxyForm = await proxyList.openAddProxyForm()
|
||||
const title = 'Incorrectly setup SOCKS5 proxy'
|
||||
await addProxyForm.addProxy({
|
||||
title: title,
|
||||
type: 'socks',
|
||||
server: 'localhost',
|
||||
port: 999,
|
||||
username: 'user',
|
||||
password: 'password'
|
||||
})
|
||||
|
||||
const assignProxy = await optionsPage.openAssignProxy()
|
||||
await assignProxy.selectForDefaultContainer(title)
|
||||
await helper.assertProxyFailure()
|
||||
})
|
||||
|
||||
after(function () {
|
||||
geckodriver.quit()
|
||||
})
|
||||
})
|
||||
|
||||
class Helper extends PageObject {
|
||||
toolbarButton = By.id('container-proxy_bekh-ivanov_me-browser-action')
|
||||
|
||||
/**
|
||||
* @return {Promise<OptionsPageObject>}
|
||||
*/
|
||||
async openOptionsPage() {
|
||||
await this._driver.setContext(firefox.Context.CHROME)
|
||||
await this.click(this.toolbarButton)
|
||||
await this._driver.setContext(firefox.Context.CONTENT)
|
||||
|
||||
let windowHandle
|
||||
await this._driver.wait(async () => {
|
||||
const windowHandles = await this._driver.getAllWindowHandles()
|
||||
windowHandle = windowHandles[windowHandles.length - 1]
|
||||
try {
|
||||
await this._driver.switchTo().window(windowHandle)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
const title = await this._driver.getTitle()
|
||||
return title === 'Container Proxy extension settings'
|
||||
}, 2000, 'Should have opened Container Proxy extension settings')
|
||||
|
||||
return this.createPageObject(OptionsPageObject)
|
||||
}
|
||||
|
||||
async assertCanGetTheIpAddress() {
|
||||
await this._driver.setContext(firefox.Context.CONTENT)
|
||||
await this._driver.get('https://duckduckgo.com/?q=ip&ia=answer&atb=v150-1')
|
||||
const text = await this._driver.getPageSource()
|
||||
|
||||
expect(text).to.include('Your IP address is')
|
||||
}
|
||||
|
||||
async assertProxyFailure() {
|
||||
await this._driver.setContext(firefox.Context.CONTENT)
|
||||
try {
|
||||
await this._driver.get('https://api.duckduckgo.com/?q=ip&no_html=1&format=json&t=firefox-container-proxy-extension')
|
||||
} catch (e) {
|
||||
}
|
||||
const text = await this._driver.getPageSource()
|
||||
|
||||
expect(text).to.include('Firefox is configured to use a proxy server that is refusing connections.')
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
version: "3"
|
||||
services:
|
||||
socks:
|
||||
build: ./socks
|
||||
ports:
|
||||
- "1080:1080"
|
||||
http:
|
||||
build: ./http
|
||||
ports:
|
||||
- "3128:3128"
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
FROM datadog/squid
|
||||
|
||||
RUN apt update && apt install -y apache2-utils
|
||||
|
||||
RUN htpasswd -b -c /etc/squid/passwords userhttp passwordhttp
|
||||
|
||||
ADD squid.conf /etc/squid/squid.conf
|
||||
|
||||
RUN chmod o+rw /var/run
|
||||
|
||||
USER proxy
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/passwords
|
||||
#auth_param basic children 5 startup = 5 idle = 1
|
||||
auth_param basic realm proxy
|
||||
auth_param basic credentialsttl 1 second
|
||||
connect_timeout 1 second
|
||||
acl authenticated proxy_auth REQUIRED
|
||||
http_access allow authenticated
|
||||
|
||||
logformat splunk_recommended_squid %ts.%03tu logformat=splunk_recommended_squid duration=%tr src_ip=%>a src_port=%>p dest_ip=%<a dest_port=%<p user_ident="%[ui" user="%[un" local_time=[%tl] http_method=%rm request_method_from_client=%<rm request_method_to_server=%>rm url="%ru" http_referrer="%{Referer}>h" http_user_agent="%{User-Agent}>h" status=%>Hs vendor_action=%Ss dest_status=%Sh total_time_milliseconds=%<tt http_content_type="%mt" bytes=%st bytes_in=%>st bytes_out=%<st sni=ssl::>sni
|
||||
|
||||
logfile_rotate 0
|
||||
cache_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||
access_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||
cache_store_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||
|
||||
# Choose the port you want. Below we set it to default 3128.
|
||||
http_port 3128
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
|
||||
user:$apr1$L24dGKxY$xqj/npbeiBkMZEH7s9Iae/
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
FROM vimagick/dante
|
||||
|
||||
RUN useradd user && echo user:password | chpasswd
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import BackgroundMain, { doNotProxy } from '../../src/background/BackgroundMain'
|
||||
import { Store } from '../../src/store/Store'
|
||||
import webExtensionsApiFake from 'webextensions-api-fake'
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { ProxySettings } from '../../src/domain/ProxySettings'
|
||||
const tryFromDao = ProxySettings.tryFromDao
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
|
||||
const store = new Store()
|
||||
|
||||
describe('BackgroundMain', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error
|
||||
global.browser = webExtensionsApiFake()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// @ts-expect-error
|
||||
delete global.browser
|
||||
})
|
||||
|
||||
const backgroundMain = new BackgroundMain({ store: store })
|
||||
|
||||
// TODO: Add test for proxyDNS property
|
||||
|
||||
describe('onRequest', function () {
|
||||
it('should return empty array if no proxy is set up', async () => {
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||
|
||||
expect(result).to.be.deep.equal(doNotProxy)
|
||||
})
|
||||
|
||||
it('should return proxy if proxy is set up', async () => {
|
||||
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||
|
||||
expect(result).to.be.an('array')
|
||||
expect(result).to.be.not.empty
|
||||
})
|
||||
|
||||
it('should remove doNotProxyLocal flag from proxy settings if proxy is set up', async () => {
|
||||
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||
|
||||
expect((result[0] as any).doNotProxyLocal).to.be.undefined
|
||||
})
|
||||
|
||||
it('should return proxy for the container if url is invalid', async () => {
|
||||
// To be more on a safe side
|
||||
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'np-protocol-url.com', tabId: 0 })
|
||||
|
||||
expect(result).to.be.an('array')
|
||||
expect(result).to.be.not.empty
|
||||
})
|
||||
|
||||
// Connections to localhost, 127.0.0.1, and ::1 are never proxied. (From FF settings)
|
||||
const localAddresses = [
|
||||
'http://localhost/index.html',
|
||||
'https://localhost/index.html',
|
||||
'http://127.0.0.1/',
|
||||
'https://127.0.0.1/',
|
||||
'http://[::1]/test',
|
||||
'https://[::1]/test',
|
||||
'http://[0:0:0:0:0:0:0:1]/test',
|
||||
'https://[0:0:0:0:0:0:0:1]/test',
|
||||
'https://user:password@127.0.0.1:123/',
|
||||
'http://[::1]:123/test'
|
||||
]
|
||||
|
||||
describe('proxying of local addresses is disabled', () => {
|
||||
localAddresses.forEach(url => {
|
||||
it(`should return empty array if the address is local: ${url}`, async () => {
|
||||
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: true })
|
||||
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
|
||||
|
||||
expect(result).to.be.deep.equal(doNotProxy)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('proxying of local addresses is enabled', () => {
|
||||
localAddresses.forEach(url => {
|
||||
it(`should return array with proxy: ${url}`, async () => {
|
||||
const host = 'proxyX.example.com'
|
||||
await givenSomeProxyIsSetUpForContainer({ host, containerId: 'container1', doNotProxyLocal: false })
|
||||
|
||||
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
|
||||
|
||||
expect(result[0].host).to.be.equal(host)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function givenSomeProxyIsSetUpForContainer({ host, containerId, doNotProxyLocal }: any): Promise<void> {
|
||||
const proxyId = 'proxy1'
|
||||
const proxy: any = {
|
||||
id: proxyId,
|
||||
type: 'socks',
|
||||
host: (host as string) ?? 'example.com',
|
||||
port: 1080
|
||||
}
|
||||
if (typeof doNotProxyLocal !== 'undefined') {
|
||||
proxy.doNotProxyLocal = doNotProxyLocal
|
||||
}
|
||||
await store.putProxy(tryFromDao(proxy) as ProxySettings)
|
||||
|
||||
await store.setContainerProxyRelation(containerId, proxyId)
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
import { ProxyDao, Store } from '../../src/store/Store'
|
||||
import webExtensionsApiFake from 'webextensions-api-fake'
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { ProxySettings } from '../../src/domain/ProxySettings'
|
||||
const tryFromDao = ProxySettings.tryFromDao
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
|
||||
describe('Store', () => {
|
||||
const store = new Store()
|
||||
|
||||
beforeEach(() => {
|
||||
(global as any).browser = webExtensionsApiFake()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete (global as any).browser
|
||||
})
|
||||
|
||||
function someProxyWith(id: string, props: Partial<ProxyDao> = {}): ProxySettings {
|
||||
const dao = {
|
||||
id: id,
|
||||
title: 'some title',
|
||||
type: 'socks',
|
||||
host: 'localhost',
|
||||
port: 1080,
|
||||
username: 'user',
|
||||
password: 'password',
|
||||
proxyDNS: true,
|
||||
failoverTimeout: 5,
|
||||
doNotProxyLocal: true
|
||||
}
|
||||
return tryFromDao({ ...dao, ...props }) as ProxySettings
|
||||
}
|
||||
|
||||
describe('getAllProxies', function () {
|
||||
it('should return empty array if nothing is saved', async () => {
|
||||
const proxies = await store.getAllProxies()
|
||||
|
||||
expect(proxies).to.be.an('array')
|
||||
expect(proxies).to.be.empty
|
||||
})
|
||||
})
|
||||
|
||||
it('should put and get the proxy back', async () => {
|
||||
const id = 'someId'
|
||||
const proxy = someProxyWith(id)
|
||||
|
||||
await store.putProxy(proxy)
|
||||
|
||||
const gotProxy = await store.getProxyById(id)
|
||||
|
||||
expect(gotProxy).to.be.deep.equal(proxy)
|
||||
})
|
||||
|
||||
it('when get, returns doNotProxyLocal `true` if was not set when put', async () => {
|
||||
const id = 'someId'
|
||||
const proxy = someProxyWith(id)
|
||||
delete (proxy as any).doNotProxyLocal
|
||||
|
||||
await store.putProxy(proxy)
|
||||
|
||||
const gotProxy = await store.getProxyById(id)
|
||||
|
||||
expect(gotProxy?.doNotProxyLocal).to.be.equal(true)
|
||||
})
|
||||
|
||||
it('stores doNotProxyLocal value', async () => {
|
||||
const id = 'someId'
|
||||
const proxy = someProxyWith(id, { doNotProxyLocal: false })
|
||||
|
||||
await store.putProxy(proxy)
|
||||
|
||||
const gotProxy = await store.getProxyById(id)
|
||||
|
||||
expect(gotProxy?.doNotProxyLocal).to.be.equal(false)
|
||||
})
|
||||
|
||||
it('should be able to delete proxy', async () => {
|
||||
const id = 'someId'
|
||||
await store.putProxy(someProxyWith(id))
|
||||
await store.deleteProxyById(id)
|
||||
|
||||
const result = await store.getProxyById(id)
|
||||
|
||||
expect(result).to.be.null
|
||||
})
|
||||
|
||||
// describe('getProxiesForContainer', () => {
|
||||
// it('should find proxy by container id if one present', async () => {
|
||||
// const cookieStoreId = 'container-1'
|
||||
// const proxyId = 'proxy-1'
|
||||
// // TODO Store should probably take care of this
|
||||
// const relations = {
|
||||
// [cookieStoreId]: [proxyId]
|
||||
// }
|
||||
// const givenProxy = someProxyWith(proxyId)
|
||||
// await store.putProxy(givenProxy)
|
||||
|
||||
// await browser.storage.local.set({ relations: relations })
|
||||
|
||||
// const [gotProxy] = await store.getProxiesForContainer(cookieStoreId)
|
||||
|
||||
// expect(gotProxy).to.be.deep.equal(givenProxy)
|
||||
// })
|
||||
|
||||
// it('doNotProxyLocal should be `true` if not set', async () => {
|
||||
// const cookieStoreId = 'container-1'
|
||||
// const proxyId = 'proxy-1'
|
||||
// // TODO Store should probably take care of this
|
||||
// const relations = {
|
||||
// [cookieStoreId]: [proxyId]
|
||||
// }
|
||||
// const givenProxy = someProxyWith(proxyId)
|
||||
// delete (givenProxy as any).doNotProxyLocal
|
||||
// await store.putProxy(givenProxy)
|
||||
|
||||
// await browser.storage.local.set({ relations: relations })
|
||||
|
||||
// const [gotProxy] = await store.getProxiesForContainer(cookieStoreId)
|
||||
|
||||
// expect(gotProxy.doNotProxyLocal).to.be.equal(true)
|
||||
// })
|
||||
|
||||
// it('should return empty array if proxy not set for the container', async () => {
|
||||
// const cookieStoreId = 'container-1'
|
||||
|
||||
// const result = await store.getProxiesForContainer(cookieStoreId)
|
||||
|
||||
// expect(result).to.be.empty
|
||||
// })
|
||||
|
||||
// it('should return empty array if proxy is set for the container but does not exist', async () => {
|
||||
// const cookieStoreId = 'container-1'
|
||||
// const proxyId = 'something-absent'
|
||||
// const relations = {
|
||||
// [cookieStoreId]: [proxyId]
|
||||
// }
|
||||
// await browser.storage.local.set({ relations: relations })
|
||||
|
||||
// const result = await store.getProxiesForContainer(cookieStoreId)
|
||||
|
||||
// expect(result).to.be.empty
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('setContainerProxyRelation', function () {
|
||||
it('should set the relation', async () => {
|
||||
await store.setContainerProxyRelation('container1', 'proxy1')
|
||||
|
||||
const relations = await store.getRelations()
|
||||
|
||||
expect(relations.container1).to.be.deep.equal(['proxy1'])
|
||||
})
|
||||
|
||||
it('should not remove existing relations', async () => {
|
||||
await store.setContainerProxyRelation('container1', 'proxy1')
|
||||
await store.setContainerProxyRelation('container2', 'proxy2')
|
||||
|
||||
const relations = await store.getRelations()
|
||||
|
||||
expect(relations.container1).to.be.deep.equal(['proxy1'])
|
||||
expect(relations.container2).to.be.deep.equal(['proxy2'])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"target": "es2019",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"test/unit/**/*",
|
||||
"declarations.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable */
|
||||
const path = require('path')
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
|
||||
const CopyPlugin = require('copy-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
background: './src/background/index.ts',
|
||||
},
|
||||
devtool: 'source-map',
|
||||
mode: 'development',
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
node: false,
|
||||
optimization: {
|
||||
minimize: false,
|
||||
moduleIds: 'named',
|
||||
chunkIds: 'named',
|
||||
concatenateModules: false,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.module.scss'],
|
||||
plugins: [new TsconfigPathsPlugin()],
|
||||
},
|
||||
output: {
|
||||
filename: '[name]/index.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.module\.scss$/i,
|
||||
use: ['style-loader', 'css-modules-typescript-loader', {
|
||||
loader: 'css-loader',
|
||||
options: { modules: { localIdentName: '[name]__[local]--[hash:base64:5]' } }
|
||||
}, 'sass-loader'],
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
'README.md',
|
||||
'LICENSE',
|
||||
'src/manifest.json',
|
||||
],
|
||||
}),
|
||||
],
|
||||
devServer: {
|
||||
hot: false,
|
||||
inline: false,
|
||||
writeToDisk: true,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user