From de538a706d69a631c22bac737ea02db547f7b9b8 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Mon, 19 Feb 2024 14:21:46 +0100 Subject: [PATCH] Allow proxy to be set per domain instead of the whole browser This is a major refactor of the way proxy work in the extension. Instead of a proxy config being used for the whole browser, each requests are intercepted and can use a specific proxy config based on arbitrary rules. The use of the pageAction is removed, because it is non-standard and will eventually be removed by Firefox. Finally, the interface has been adapted and improved. --- .vscode/extensions.json | 2 +- README.md | 2 + extension/assets/route.svg | 7 - package-lock.json | 4 +- package.json | 4 +- scripts/prepare.ts | 19 +- src/background/main.ts | 15 +- .../ConnectionDetails/ConnectionDetails.vue | 39 ++-- .../ConnectionDetails/LocationDrawer.vue | 1 - .../UsingMullvadConnectionStatus.vue | 6 +- src/components/Headers/HomeHeader.vue | 39 ++++ .../Headers}/LicenseHeader.vue | 0 src/components/Headers/ProxyHeader.vue | 39 ++++ src/components/Headers/SettingsHeader.vue | 39 ++++ src/components/IconLabel.vue | 18 +- src/components/Icons/FeGlobe.vue | 18 ++ src/components/Icons/TaRouteBlocked.vue | 22 +++ src/components/Location.test.ts | 43 ++--- src/components/Location.vue | 90 +++++---- src/components/LocationTabs.vue | 10 +- .../MostUsedLocationButtons.test.ts | 44 +++-- src/components/MostUsedLocationButtons.vue | 13 +- .../PrivacyRecommendations/WebRTCToggle.vue | 2 +- src/components/Proxy/CurrentProxyDetails.vue | 17 ++ src/components/Proxy/ProxyGlobal.vue | 59 ++++++ src/components/Proxy/ProxyHost.vue | 95 ++++++++++ src/components/ProxyDetails/ProxyButton.vue | 57 ------ src/components/ProxyDetails/ProxyConnect.vue | 33 ---- src/components/ProxyDetails/ProxyDetails.vue | 21 --- .../ProxyDetails/ProxyDisconnectMessage.vue | 11 -- src/components/RecentLocationButtons.vue | 10 +- .../__snapshots__/Location.test.ts.snap | 73 +------- src/composables/useActiveTab.ts | 23 +++ .../HistoricConnections.types.ts | 8 - .../useHistoricConnections.ts | 81 -------- .../{useSocksProxies.ts => useListProxies.ts} | 35 +++- src/composables/useLocations.ts | 4 +- .../useProxyHistory/HistoryEntries.types.ts | 13 ++ .../useProxyHistory/useProxyHistory.ts | 70 +++++++ src/composables/useSocksProxy.ts | 174 +++++++++++------- src/composables/useStore.ts | 36 +++- .../utils/getSocksIpForProtocol.test.ts | 23 --- .../utils/getSocksIpForProtocol.ts | 22 --- src/helpers/browserAction.ts | 71 +++++++ src/helpers/connCheck.types.ts | 3 +- src/helpers/getRandomSocksProxy.test.ts | 28 ++- src/helpers/getRandomSocksProxy.ts | 15 +- src/helpers/pageAction.ts | 47 ----- src/helpers/socks.ts | 19 -- src/helpers/socksProxy.ts | 107 +++++++++++ src/helpers/socksProxy.types.ts | 58 ++++++ src/manifest.ts | 19 +- src/popup/App.vue | 18 +- src/popup/Popup.vue | 12 +- src/popup/headers/HomeHeader.vue | 33 ---- src/popup/headers/SettingsHeader.vue | 29 --- src/popup/routes.ts | 4 +- src/popup/views/Home.vue | 35 +++- src/popup/views/Proxy.vue | 32 ++++ src/styles/main.css | 4 +- vite.config.ts => vite.config.mts | 0 61 files changed, 1137 insertions(+), 738 deletions(-) delete mode 100644 extension/assets/route.svg create mode 100644 src/components/Headers/HomeHeader.vue rename src/{popup/headers => components/Headers}/LicenseHeader.vue (100%) create mode 100644 src/components/Headers/ProxyHeader.vue create mode 100644 src/components/Headers/SettingsHeader.vue create mode 100644 src/components/Icons/FeGlobe.vue create mode 100644 src/components/Icons/TaRouteBlocked.vue create mode 100644 src/components/Proxy/CurrentProxyDetails.vue create mode 100644 src/components/Proxy/ProxyGlobal.vue create mode 100644 src/components/Proxy/ProxyHost.vue delete mode 100644 src/components/ProxyDetails/ProxyButton.vue delete mode 100644 src/components/ProxyDetails/ProxyConnect.vue delete mode 100644 src/components/ProxyDetails/ProxyDetails.vue delete mode 100644 src/components/ProxyDetails/ProxyDisconnectMessage.vue create mode 100644 src/composables/useActiveTab.ts delete mode 100644 src/composables/useHistoricConnections/HistoricConnections.types.ts delete mode 100644 src/composables/useHistoricConnections/useHistoricConnections.ts rename src/composables/{useSocksProxies.ts => useListProxies.ts} (70%) create mode 100644 src/composables/useProxyHistory/HistoryEntries.types.ts create mode 100644 src/composables/useProxyHistory/useProxyHistory.ts delete mode 100644 src/composables/utils/getSocksIpForProtocol.test.ts delete mode 100644 src/composables/utils/getSocksIpForProtocol.ts create mode 100644 src/helpers/browserAction.ts delete mode 100644 src/helpers/pageAction.ts delete mode 100644 src/helpers/socks.ts create mode 100644 src/helpers/socksProxy.ts create mode 100644 src/helpers/socksProxy.types.ts delete mode 100644 src/popup/headers/HomeHeader.vue delete mode 100644 src/popup/headers/SettingsHeader.vue create mode 100644 src/popup/views/Proxy.vue rename vite.config.ts => vite.config.mts (100%) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4112095b..581e5236 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,6 @@ { "recommendations": [ - "johnsoncodehk.volar", + "Vue.volar", "dbaeumer.vscode-eslint", "voorjaar.windicss-intellisense", "csstools.postcss" diff --git a/README.md b/README.md index d6fdca9d..88a1db51 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,10 @@ Mullvad Browser Extension requires the following permissions: - `proxy` to configure and use Mullvad proxy servers - `storage` to save preferences - `search` to recommend other search engines +- `tabs` to be able to show proxy settings based on the active tab - `*://*.mullvad.net/*` to get proxy servers list and display your connection information (See `Network requests` for details) +- `` to have granular proxy settings _Permissions are automatically accepted when testing the extension._ diff --git a/extension/assets/route.svg b/extension/assets/route.svg deleted file mode 100644 index 89d5cff2..00000000 --- a/extension/assets/route.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 48110aab..296000fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mullvad-browser-extension", - "version": "0.8.5", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mullvad-browser-extension", - "version": "0.8.5", + "version": "0.9.0", "dependencies": { "axios": "^1.6.7", "naive-ui": "^2.37.3", diff --git a/package.json b/package.json index 0243a72c..53c8b399 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mullvad-browser-extension", "displayName": "Mullvad Browser Extension", - "version": "0.8.5", + "version": "0.9.0", "description": "Improve your Mullvad VPN experience, in your browser.", "private": true, "engines": { @@ -22,7 +22,7 @@ "pack:xpi": "cross-env WEB_EXT_ARTIFACTS_DIR=./ web-ext build --source-dir ./extension --filename mullvad-browser-extension-$npm_package_version.xpi --overwrite-dest", "start:chromium": "web-ext run --source-dir ./extension --target=chromium", "start": "web-ext run --source-dir ./extension --target=firefox-desktop --start-url=about:debugging#/runtime/this-firefox", - "restart": "web-ext run --firefox-profile ~/.cache/mozilla/firefox/md0hl4g1.MBE-Testing-Profile --keep-profile-changes --source-dir ./extension --target=firefox-desktop --start-url=about:debugging#/runtime/this-firefox", + "restart": "web-ext run --firefox-profile ~/.mozilla/firefox/md0hl4g1.MBE-Testing-Profile --keep-profile-changes --source-dir ./extension --target=firefox-desktop --start-url=about:debugging#/runtime/this-firefox", "clear": "rimraf extension/dist extension/manifest.json extension/README.md extension/LICENSE.md mullvad-browser-extension* key.pem", "lint": "npm run eslint && npm run lint:style", "eslint": "eslint . --ext=.ts,.js,.vue", diff --git a/scripts/prepare.ts b/scripts/prepare.ts index 741e1333..58a7821a 100644 --- a/scripts/prepare.ts +++ b/scripts/prepare.ts @@ -8,10 +8,7 @@ import { r, port, isDev, log } from './utils'; * Stub index.html to use Vite in development */ async function stubIndexHtml() { - const views = [ - 'popup', - 'background', - ]; + const views = ['popup', 'background']; for (const view of views) { await fs.ensureDir(r(`extension/dist/${view}`)); @@ -32,12 +29,10 @@ writeManifest(); if (isDev) { stubIndexHtml(); - chokidar.watch(r('src/**/*.html')) - .on('change', () => { - stubIndexHtml(); - }); - chokidar.watch([r('src/manifest.ts'), r('package.json')]) - .on('change', () => { - writeManifest(); - }); + chokidar.watch(r('src/**/*.html')).on('change', () => { + stubIndexHtml(); + }); + chokidar.watch([r('src/manifest.ts'), r('package.json')]).on('change', () => { + writeManifest(); + }); } diff --git a/src/background/main.ts b/src/background/main.ts index 4eef28e4..331beafd 100644 --- a/src/background/main.ts +++ b/src/background/main.ts @@ -1,7 +1,6 @@ -import { onMessage } from 'webext-bridge/background'; - import { addExtListeners } from '@/helpers/extensions'; -import { initPageAction, updateTabs } from '@/helpers/pageAction'; +import { initBrowserAction } from '@/helpers/browserAction'; +import { initProxyRequests } from '@/helpers/socksProxy'; // only on dev mode if (import.meta.hot) { @@ -12,10 +11,8 @@ if (import.meta.hot) { // Add listeners on extension actions addExtListeners(); -// Update pageAction in tabs and add listeners -initPageAction(); +// Update browserAction for tabs and add listeners +initBrowserAction(); -// Add message listeners -onMessage('update-socks', () => { - updateTabs(); -}); +// Add listener for proxy requests +initProxyRequests(); diff --git a/src/components/ConnectionDetails/ConnectionDetails.vue b/src/components/ConnectionDetails/ConnectionDetails.vue index d4ed57e0..89355399 100644 --- a/src/components/ConnectionDetails/ConnectionDetails.vue +++ b/src/components/ConnectionDetails/ConnectionDetails.vue @@ -1,39 +1,42 @@ diff --git a/src/components/ConnectionDetails/LocationDrawer.vue b/src/components/ConnectionDetails/LocationDrawer.vue index 365267cf..4955fd05 100644 --- a/src/components/ConnectionDetails/LocationDrawer.vue +++ b/src/components/ConnectionDetails/LocationDrawer.vue @@ -2,7 +2,6 @@ import { NDrawer, NDrawerContent } from 'naive-ui'; import Location from '@/components/Location.vue'; - import useLocations from '@/composables/useLocations'; const { showLocations } = useLocations(); diff --git a/src/components/ConnectionStatus/UsingMullvadConnectionStatus.vue b/src/components/ConnectionStatus/UsingMullvadConnectionStatus.vue index 108e7d64..55d0bd69 100644 --- a/src/components/ConnectionStatus/UsingMullvadConnectionStatus.vue +++ b/src/components/ConnectionStatus/UsingMullvadConnectionStatus.vue @@ -1,15 +1,11 @@ + + diff --git a/src/popup/headers/LicenseHeader.vue b/src/components/Headers/LicenseHeader.vue similarity index 100% rename from src/popup/headers/LicenseHeader.vue rename to src/components/Headers/LicenseHeader.vue diff --git a/src/components/Headers/ProxyHeader.vue b/src/components/Headers/ProxyHeader.vue new file mode 100644 index 00000000..28e23013 --- /dev/null +++ b/src/components/Headers/ProxyHeader.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/Headers/SettingsHeader.vue b/src/components/Headers/SettingsHeader.vue new file mode 100644 index 00000000..6ef42574 --- /dev/null +++ b/src/components/Headers/SettingsHeader.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/IconLabel.vue b/src/components/IconLabel.vue index 87e2eb45..349daef7 100644 --- a/src/components/IconLabel.vue +++ b/src/components/IconLabel.vue @@ -10,11 +10,11 @@ import FeDrop from '@/components/Icons/FeDrop.vue'; export type IconType = 'warning' | 'success' | 'info' | 'spinner' | 'leak' | 'check'; -defineProps<{ text: string; type: IconType }>(); +defineProps<{ text?: string; type: IconType }>(); - - diff --git a/src/components/Icons/FeGlobe.vue b/src/components/Icons/FeGlobe.vue new file mode 100644 index 00000000..64b4fd22 --- /dev/null +++ b/src/components/Icons/FeGlobe.vue @@ -0,0 +1,18 @@ + diff --git a/src/components/Icons/TaRouteBlocked.vue b/src/components/Icons/TaRouteBlocked.vue new file mode 100644 index 00000000..004962c2 --- /dev/null +++ b/src/components/Icons/TaRouteBlocked.vue @@ -0,0 +1,22 @@ + diff --git a/src/components/Location.test.ts b/src/components/Location.test.ts index 480ab825..1f3a8567 100644 --- a/src/components/Location.test.ts +++ b/src/components/Location.test.ts @@ -2,46 +2,33 @@ import { mount } from '@vue/test-utils'; import { NCollapseItem } from 'naive-ui'; import Location from '@/components/Location.vue'; -import useSocksProxies from '@/composables/useSocksProxies'; +import useListProxies from '@/composables/useListProxies'; -jest.mock('@/composables/useSocksProxies', () => ({ +jest.mock('@/composables/useListProxies', () => ({ __esModule: true, default: jest.fn(), })); -jest.mock('@/composables/useSocksProxy', () => ({ - __esModule: true, - default: () => ({ connectToSocksProxy: jest.fn() }), -})); - describe('Location', () => { - it('should show a loading message', () => { - (useSocksProxies as jest.Mock).mockReturnValueOnce({ isLoading: true }); - const wrapper = mount(Location); - expect(wrapper.text()).toMatch(/loading/i); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('should show an error message', () => { - (useSocksProxies as jest.Mock).mockReturnValueOnce({ isError: true, error: 'Network error' }); - const wrapper = mount(Location); - expect(wrapper.text()).toMatch(/network error/i); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('should show two countries', () => { - (useSocksProxies as jest.Mock).mockReturnValueOnce({ - data: [ - { country: 'Australia' }, - { country: 'Austria' }, + it('should show two countries', async () => { + (useListProxies as jest.Mock).mockReturnValueOnce({ + proxiesList: [ + { + country: 'Albania', + }, + { + country: 'Australia', + }, ], }); + const wrapper = mount(Location); + await wrapper.vm.$nextTick(); const countries = wrapper.findAllComponents(NCollapseItem); expect(countries).toHaveLength(2); - expect(countries[0]?.text()).toMatch(/australia/i); - expect(countries[1]?.text()).toMatch(/austria/i); + expect(countries[0]?.text()).toMatch(/albania/i); + expect(countries[1]?.text()).toMatch(/australia/i); expect(wrapper.element).toMatchSnapshot(); }); diff --git a/src/components/Location.vue b/src/components/Location.vue index 0d47004a..8982d02c 100644 --- a/src/components/Location.vue +++ b/src/components/Location.vue @@ -1,48 +1,73 @@