├── .nvmrc
├── install.sh
├── src
├── packages
│ └── rq_proxy
│ │ ├── index.js
│ │ └── ssl-proxying
│ │ └── ssl-proxying-manager.ts
├── main
│ ├── actions
│ │ ├── events
│ │ │ ├── constants.js
│ │ │ └── index.ts
│ │ ├── getCurrentNetworkLogs.js
│ │ ├── logNetworkRequest.js
│ │ ├── logNetworkRequestV2.js
│ │ ├── stateManagement.js
│ │ ├── initPrimaryStorage.js
│ │ ├── cleanup.js
│ │ ├── setupIPCForwarding.js
│ │ ├── initGlobalState.js
│ │ ├── getProxiedAxios.ts
│ │ ├── cookiesHelpers.ts
│ │ ├── startBackgroundProcess.js
│ │ └── networkSessionStorage
│ │ │ └── index.js
│ ├── preload-apis
│ │ ├── AppState.js
│ │ ├── DesktopStorageService.js
│ │ └── IPC.js
│ └── preload.js
├── renderer
│ ├── lib
│ │ ├── proxy-interface
│ │ │ ├── base.ts
│ │ │ ├── rulesFetcher.js
│ │ │ ├── userPreferenceFetcher.ts
│ │ │ ├── ssl-proxying-config-fetcher.ts
│ │ │ └── loggerService.js
│ │ └── RPCServiceOverIPC.ts
│ ├── actions
│ │ ├── shell
│ │ │ ├── deleteItem.js
│ │ │ └── index.js
│ │ ├── local-sync
│ │ │ ├── file-types
│ │ │ │ ├── file-type.interface.ts
│ │ │ │ └── file-types.ts
│ │ │ ├── constants.ts
│ │ │ ├── fsIgnore-manager.ts
│ │ │ ├── fs
│ │ │ │ ├── access-fallback.decorator.ts
│ │ │ │ ├── fs-unix.ts
│ │ │ │ ├── sudoCommandExecutor.ts
│ │ │ │ └── fs.service.ts
│ │ │ ├── decorators
│ │ │ │ └── handle-error.decorator.ts
│ │ │ └── fs-manager-builder.rpc-service.ts
│ │ ├── getNextAvailablePort.js
│ │ ├── proxy
│ │ │ └── getProxyConfig.ts
│ │ ├── stateManagement
│ │ │ └── index.js
│ │ ├── storage
│ │ │ ├── primaryStorage
│ │ │ │ └── index.js
│ │ │ ├── cacheUtils.ts
│ │ │ ├── initPrimaryStorageCache.js
│ │ │ ├── initRulesCache.js
│ │ │ └── initGroupsCache.js
│ │ ├── local-files
│ │ │ ├── actions.ts
│ │ │ ├── sample
│ │ │ │ ├── base.ts
│ │ │ │ └── service.ts
│ │ │ └── index.ts
│ │ ├── apps
│ │ │ ├── os
│ │ │ │ ├── ca
│ │ │ │ │ ├── windows.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── osx.js
│ │ │ │ │ └── utils.js
│ │ │ │ ├── system-wide.js
│ │ │ │ └── proxy
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── utils.js
│ │ │ ├── mobile
│ │ │ │ ├── utils.ts
│ │ │ │ ├── adb-commands.ts
│ │ │ │ └── iosSimulator.js
│ │ │ ├── browsers
│ │ │ │ ├── safari.js
│ │ │ │ ├── close-warning-tab.js
│ │ │ │ └── browser-handler.js
│ │ │ └── appManager.js
│ │ ├── shutdown.js
│ │ ├── saveRootCert.js
│ │ ├── fileManagement
│ │ │ └── index.js
│ │ └── startHelperServer.js
│ ├── index.ejs
│ ├── config
│ │ ├── sub
│ │ │ └── staticFilesDirectory.js
│ │ └── index.js
│ ├── utils
│ │ ├── storage
│ │ │ └── index.js
│ │ ├── cert
│ │ │ ├── generateSPKIFingerprint.js
│ │ │ └── index.ts
│ │ ├── misc
│ │ │ └── index.js
│ │ └── userPreferencesManager.js
│ ├── types
│ │ └── index.ts
│ ├── index.js
│ └── services
│ │ └── storage-cache.ts
├── lib
│ ├── storage
│ │ ├── types
│ │ │ ├── offline-log-config.ts
│ │ │ ├── user-preference.ts
│ │ │ ├── ssl-proxying.ts
│ │ │ ├── storage-action.ts
│ │ │ └── action-types.ts
│ │ ├── constants
│ │ │ └── index.ts
│ │ ├── action-processors
│ │ │ ├── base.ts
│ │ │ ├── accessed-files.ts
│ │ │ ├── user-preference.ts
│ │ │ └── ssl-proxying.ts
│ │ ├── schemas
│ │ │ └── userPreferenceSchema.ts
│ │ ├── store-wrapper.ts
│ │ └── index.ts
│ └── autoupdate
│ │ └── index.js
└── utils
│ ├── index.ts
│ ├── sentryInit.js
│ ├── logger.js
│ ├── circularQueue.js
│ └── envDetection.js
├── release
└── app
│ ├── static
│ ├── sample.txt
│ ├── nss
│ │ ├── darwin
│ │ │ ├── mar
│ │ │ ├── certutil
│ │ │ ├── mbsdiff
│ │ │ ├── modutil
│ │ │ ├── pk12util
│ │ │ ├── signmar
│ │ │ ├── shlibsign
│ │ │ ├── libnss3.dylib
│ │ │ ├── libfreebl3.dylib
│ │ │ ├── libmozglue.dylib
│ │ │ ├── libnssckbi.dylib
│ │ │ ├── libnssdbm3.dylib
│ │ │ └── libsoftokn3.dylib
│ │ ├── linux
│ │ │ ├── mar
│ │ │ ├── certutil
│ │ │ ├── mbsdiff
│ │ │ ├── modutil
│ │ │ ├── pk12util
│ │ │ ├── shlibsign
│ │ │ ├── signmar
│ │ │ ├── libnspr4.so
│ │ │ ├── libnss3.so
│ │ │ ├── libplc4.so
│ │ │ ├── libplds4.so
│ │ │ ├── libssl3.so
│ │ │ ├── libnssckbi.so
│ │ │ ├── libnssdbm3.so
│ │ │ ├── libnssutil3.so
│ │ │ ├── libsmime3.so
│ │ │ ├── libsoftokn3.so
│ │ │ ├── libmozsqlite3.so
│ │ │ └── libfreeblpriv3.so
│ │ ├── win32
│ │ │ ├── mar
│ │ │ ├── mbsdiff
│ │ │ ├── nss3.dll
│ │ │ ├── freebl3.dll
│ │ │ ├── modutil.exe
│ │ │ ├── mozglue.dll
│ │ │ ├── nssckbi.dll
│ │ │ ├── nssdbm3.dll
│ │ │ ├── signmar.exe
│ │ │ ├── certutil.exe
│ │ │ ├── pk12util.exe
│ │ │ ├── shlibsign.exe
│ │ │ └── softokn3.dll
│ │ └── README.md
│ └── overrides
│ │ ├── java-agent.jar
│ │ ├── pythonpath
│ │ ├── stripe.py
│ │ ├── http
│ │ │ ├── __init__.py
│ │ │ └── client.py
│ │ ├── httptoolkit_intercept.py
│ │ ├── httplib2.py
│ │ └── httplib.py
│ │ ├── gems
│ │ ├── stripe.rb
│ │ ├── uri
│ │ │ └── generic.rb
│ │ ├── http.rb
│ │ └── net
│ │ │ ├── https.rb
│ │ │ └── http.rb
│ │ ├── path
│ │ ├── node
│ │ ├── prepend.php
│ │ ├── node.bat
│ │ ├── php
│ │ └── php.bat
│ │ └── js
│ │ ├── wrap-require.js
│ │ └── prepend-node.js
│ ├── dont_run_npm_install_here.md
│ └── package.json
├── .erb
├── mocks
│ └── fileMock.js
├── img
│ ├── erb-banner.png
│ └── erb-logo.png
├── configs
│ ├── .eslintrc
│ ├── webpack.config.eslint.ts
│ ├── webpack.paths.ts
│ ├── webpack.config.base.ts
│ ├── webpack.config.renderer.dev.dll.ts
│ └── webpack.config.main.prod.ts
└── scripts
│ ├── .eslintrc
│ ├── link-modules.js
│ ├── delete-source-maps.js
│ ├── check-node-env.js
│ ├── clean.js
│ ├── check-port-in-use.js
│ ├── check-build-exists.ts
│ ├── notarize.js
│ ├── electron-rebuild.js
│ └── check-native-dep.js
├── assets
├── icon.ico
├── icon.png
├── AppIcon.png
├── icon.icns
├── AppIcon.icns
├── iconTemplate.png
├── icons
│ ├── 16x16.png
│ ├── 32x32.png
│ ├── 64x64.png
│ ├── 128x128.png
│ ├── 256x256.png
│ ├── 512x512.png
│ └── 1024x1024.png
├── iconTemplate@2x.ico
├── iconTemplate@2x.png
├── assets.d.ts
├── entitlements.mac.plist
└── requirement.rqset
├── .github
├── ISSUE_TEMPLATE
│ ├── 2-Question.md
│ ├── 3-Feature_request.md
│ └── 1-Bug_report.md
├── config.yml
├── stale.yml
└── workflows
│ └── release_desktop_app.yml
├── watch.sh
├── .gitattributes
├── .editorconfig
├── .eslintignore
├── .gitignore
├── tsconfig.json
├── .eslintrc.js
└── electron-builder-setapp.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.15.0
2 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | npm i
2 |
--------------------------------------------------------------------------------
/src/packages/rq_proxy/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/release/app/static/sample.txt:
--------------------------------------------------------------------------------
1 | HELLLLEOOOOOOOO
2 |
--------------------------------------------------------------------------------
/.erb/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default "test-file-stub";
2 |
--------------------------------------------------------------------------------
/assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icon.ico
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/AppIcon.png
--------------------------------------------------------------------------------
/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icon.icns
--------------------------------------------------------------------------------
/assets/AppIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/AppIcon.icns
--------------------------------------------------------------------------------
/.erb/img/erb-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/.erb/img/erb-banner.png
--------------------------------------------------------------------------------
/.erb/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/.erb/img/erb-logo.png
--------------------------------------------------------------------------------
/assets/iconTemplate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/iconTemplate.png
--------------------------------------------------------------------------------
/assets/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/16x16.png
--------------------------------------------------------------------------------
/assets/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/32x32.png
--------------------------------------------------------------------------------
/assets/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/64x64.png
--------------------------------------------------------------------------------
/assets/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/128x128.png
--------------------------------------------------------------------------------
/assets/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/256x256.png
--------------------------------------------------------------------------------
/assets/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/512x512.png
--------------------------------------------------------------------------------
/assets/iconTemplate@2x.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/iconTemplate@2x.ico
--------------------------------------------------------------------------------
/assets/iconTemplate@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/iconTemplate@2x.png
--------------------------------------------------------------------------------
/assets/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/assets/icons/1024x1024.png
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/mar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/mar
--------------------------------------------------------------------------------
/release/app/static/nss/linux/mar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/mar
--------------------------------------------------------------------------------
/release/app/static/nss/win32/mar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/mar
--------------------------------------------------------------------------------
/src/main/actions/events/constants.js:
--------------------------------------------------------------------------------
1 | const EVENTS = {
2 | QUIT_APP: "quit_desktop_app",
3 | };
4 |
5 | export default EVENTS;
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-Question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question.❓
4 | labels: "question"
5 | ---
6 |
7 | ## Summary
8 |
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/certutil:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/certutil
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/mbsdiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/mbsdiff
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/modutil:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/modutil
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/pk12util:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/pk12util
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/signmar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/signmar
--------------------------------------------------------------------------------
/release/app/static/nss/linux/certutil:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/certutil
--------------------------------------------------------------------------------
/release/app/static/nss/linux/mbsdiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/mbsdiff
--------------------------------------------------------------------------------
/release/app/static/nss/linux/modutil:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/modutil
--------------------------------------------------------------------------------
/release/app/static/nss/linux/pk12util:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/pk12util
--------------------------------------------------------------------------------
/release/app/static/nss/linux/shlibsign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/shlibsign
--------------------------------------------------------------------------------
/release/app/static/nss/linux/signmar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/signmar
--------------------------------------------------------------------------------
/release/app/static/nss/win32/mbsdiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/mbsdiff
--------------------------------------------------------------------------------
/release/app/static/nss/win32/nss3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/nss3.dll
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/shlibsign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/shlibsign
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libnspr4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libnspr4.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libnss3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libnss3.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libplc4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libplc4.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libplds4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libplds4.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libssl3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libssl3.so
--------------------------------------------------------------------------------
/release/app/static/nss/win32/freebl3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/freebl3.dll
--------------------------------------------------------------------------------
/release/app/static/nss/win32/modutil.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/modutil.exe
--------------------------------------------------------------------------------
/release/app/static/nss/win32/mozglue.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/mozglue.dll
--------------------------------------------------------------------------------
/release/app/static/nss/win32/nssckbi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/nssckbi.dll
--------------------------------------------------------------------------------
/release/app/static/nss/win32/nssdbm3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/nssdbm3.dll
--------------------------------------------------------------------------------
/release/app/static/nss/win32/signmar.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/signmar.exe
--------------------------------------------------------------------------------
/watch.sh:
--------------------------------------------------------------------------------
1 | echo "Linking and starting watcher in requestly-proxy"
2 |
3 | npm link ../requestly-proxy
4 |
5 | cd ../requestly-proxy
6 | npm run watch
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libnss3.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libnss3.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libnssckbi.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libnssckbi.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libnssdbm3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libnssdbm3.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libnssutil3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libnssutil3.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libsmime3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libsmime3.so
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libsoftokn3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libsoftokn3.so
--------------------------------------------------------------------------------
/release/app/static/nss/win32/certutil.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/certutil.exe
--------------------------------------------------------------------------------
/release/app/static/nss/win32/pk12util.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/pk12util.exe
--------------------------------------------------------------------------------
/release/app/static/nss/win32/shlibsign.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/shlibsign.exe
--------------------------------------------------------------------------------
/release/app/static/nss/win32/softokn3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/win32/softokn3.dll
--------------------------------------------------------------------------------
/release/app/static/overrides/java-agent.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/overrides/java-agent.jar
--------------------------------------------------------------------------------
/src/renderer/lib/proxy-interface/base.ts:
--------------------------------------------------------------------------------
1 | interface BaseConfigFetcher {
2 | getConfig: () => any;
3 | }
4 |
5 | export default BaseConfigFetcher;
6 |
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libmozsqlite3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libmozsqlite3.so
--------------------------------------------------------------------------------
/src/lib/storage/types/offline-log-config.ts:
--------------------------------------------------------------------------------
1 | export interface LogConfig {
2 | isEnabled: boolean;
3 | storePath: string;
4 | filter: string[]
5 | }
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/release/app/dont_run_npm_install_here.md:
--------------------------------------------------------------------------------
1 | unless there is no node_modules here to work with here. If so then run npm i first and wait for it to inevitably fail!
2 |
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libfreebl3.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libfreebl3.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libmozglue.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libmozglue.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libnssckbi.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libnssckbi.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libnssdbm3.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libnssdbm3.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/darwin/libsoftokn3.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/darwin/libsoftokn3.dylib
--------------------------------------------------------------------------------
/release/app/static/nss/linux/libfreeblpriv3.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/requestly/requestly-desktop-app/HEAD/release/app/static/nss/linux/libfreeblpriv3.so
--------------------------------------------------------------------------------
/.erb/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something added to the Requestly. 🎉
4 | labels: "enhancement"
5 | ---
6 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.eslint.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 |
3 | module.exports = require("./webpack.config.renderer.dev").default;
4 |
--------------------------------------------------------------------------------
/.erb/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/renderer/actions/shell/deleteItem.js:
--------------------------------------------------------------------------------
1 | import { shell } from "electron";
2 |
3 | const deleteItem = (itemPath) => {
4 | return shell.moveItemToTrash(itemPath, true);
5 | };
6 |
7 | export default deleteItem;
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.exe binary
2 | *.png binary
3 | *.jpg binary
4 | *.jpeg binary
5 | *.ico binary
6 | *.icns binary
7 | *.eot binary
8 | *.otf binary
9 | *.ttf binary
10 | *.woff binary
11 | *.woff2 binary
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/src/main/actions/getCurrentNetworkLogs.js:
--------------------------------------------------------------------------------
1 | const getCurrentNetworkLogs = () => {
2 | return new Promise((resolve) => {
3 | const currentLog = global.networkRequestsLog;
4 | resolve(currentLog);
5 | });
6 | };
7 |
8 | export default getCurrentNetworkLogs;
9 |
--------------------------------------------------------------------------------
/src/main/actions/logNetworkRequest.js:
--------------------------------------------------------------------------------
1 | const logNetworkRequest = (event, message, webAppWindow) => {
2 | const newLog = message;
3 | if (webAppWindow) {
4 | webAppWindow.send("log-network-request", newLog);
5 | }
6 | };
7 |
8 | export default logNetworkRequest;
9 |
--------------------------------------------------------------------------------
/src/renderer/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Requestly Background Process
6 |
7 |
8 | Requestly Background Process
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/actions/logNetworkRequestV2.js:
--------------------------------------------------------------------------------
1 | const logNetworkRequestV2 = (event, message, webAppWindow) => {
2 | const newLog = message;
3 | if (webAppWindow && !webAppWindow.isDestroyed()) {
4 | webAppWindow.send("log-network-request-v2", newLog);
5 | }
6 | };
7 |
8 | export default logNetworkRequestV2;
9 |
--------------------------------------------------------------------------------
/.erb/scripts/link-modules.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import {
3 | appNodeModulesPath,
4 | srcNodeModulesPath,
5 | } from "../configs/webpack.paths";
6 |
7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, "junction");
9 | }
10 |
--------------------------------------------------------------------------------
/.erb/scripts/delete-source-maps.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import rimraf from "rimraf";
3 | import webpackPaths from "../configs/webpack.paths";
4 |
5 | export default function deleteSourceMaps() {
6 | rimraf.sync(path.join(webpackPaths.distMainPath, "*.js.map"));
7 | rimraf.sync(path.join(webpackPaths.distRendererPath, "*.js.map"));
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import stream from "stream";
2 |
3 | export function stringAsStream(input: string) {
4 | const contentStream = new stream.Readable();
5 | // eslint-disable-next-line no-underscore-dangle
6 | contentStream._read = () => {};
7 | contentStream.push(input);
8 | contentStream.push(null);
9 | return contentStream;
10 | }
11 |
--------------------------------------------------------------------------------
/src/renderer/actions/shell/index.js:
--------------------------------------------------------------------------------
1 | import * as ensureCommandExists from "command-exists";
2 | import * as Sentry from "@sentry/browser";
3 |
4 | export const commandExists = (path) =>
5 | ensureCommandExists(path)
6 | .then(() => true)
7 | .catch((error) => {
8 | Sentry.captureException(error);
9 | return false;
10 | });
11 |
--------------------------------------------------------------------------------
/assets/assets.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint @typescript-eslint/no-explicit-any: off */
2 |
3 | declare module "*.svg" {
4 | const content: string;
5 | export default content;
6 | }
7 |
8 | declare module "*.png" {
9 | const content: string;
10 | export default content;
11 | }
12 |
13 | declare module "*.jpg" {
14 | const content: string;
15 | export default content;
16 | }
17 |
--------------------------------------------------------------------------------
/assets/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-unsigned-executable-memory
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/storage/types/user-preference.ts:
--------------------------------------------------------------------------------
1 | export interface UserPreferenceObj {
2 | defaultPort: number;
3 | localFileLogConfig: {
4 | isEnabled: boolean;
5 | storePath: string;
6 | filter: string[]
7 | };
8 | }
9 |
10 | export interface ISource {
11 | defaultPort: number;
12 | isLocalLoggingEnabled: boolean;
13 | logStorePath: string;
14 | localLogFilterfilter: string[]
15 | }
16 |
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/stripe.py:
--------------------------------------------------------------------------------
1 | from httptoolkit_intercept import preload_real_module
2 | preload_real_module('stripe')
3 |
4 | import stripe, os
5 | stripe.ca_bundle_path = os.environ['SSL_CERT_FILE']
6 |
7 | # Re-export all public fields from Stripe
8 | from stripe import *
9 | # Load a few extra notable private fields, for max compatibility
10 | from stripe import __path__, __file__, __doc__
--------------------------------------------------------------------------------
/src/lib/storage/types/ssl-proxying.ts:
--------------------------------------------------------------------------------
1 | export interface SSLProxyingJsonObj {
2 | enabledAll: boolean;
3 | inclusionList: {};
4 | exclusionList: {};
5 | }
6 |
7 | // TODO @sahil: Move this to common/constants
8 | export interface ISource {
9 | id: string;
10 | filters: any;
11 | key: "Url" | "host" | "path";
12 | operator: "Equals" | "Contains" | "Matches" | "Wildcard_Matches";
13 | value: any;
14 | }
15 |
--------------------------------------------------------------------------------
/src/renderer/config/sub/staticFilesDirectory.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const isDevelopment =
3 | process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
4 |
5 | const STATIC_FILES_DIR = isDevelopment
6 | ? path.resolve(__dirname, "../../../../../../release/app/static")
7 | : path.join(__dirname, "../../../static"); // Resources Folder
8 |
9 | module.exports = STATIC_FILES_DIR;
10 |
--------------------------------------------------------------------------------
/src/main/actions/events/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import { BrowserWindow } from "electron";
3 |
4 | export const trackEventViaWebApp = (
5 | webAppWindow: BrowserWindow,
6 | eventName: string,
7 | eventParams: Record = {}
8 | ) => {
9 | webAppWindow.webContents.send("analytics-event", {
10 | name: eventName,
11 | params: eventParams,
12 | origin: "main",
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/release/app/static/overrides/gems/stripe.rb:
--------------------------------------------------------------------------------
1 | # Remove this module from LOAD_PATH, so we can load the real one
2 | gem_override_path = File.expand_path(__dir__)
3 | $LOAD_PATH.reject! { |path| File.expand_path(path) == gem_override_path }
4 |
5 | # Load stripe, and inject our certificate
6 | require 'stripe'
7 | Stripe.ca_bundle_path=ENV['SSL_CERT_FILE']
8 |
9 | # Put this override directory back on LOAD_PATH
10 | $LOAD_PATH.unshift(gem_override_path)
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/file-types/file-type.interface.ts:
--------------------------------------------------------------------------------
1 | import { Static, TSchema } from "@sinclair/typebox";
2 | import { ContentParseResult, FileTypeEnum } from "../types";
3 |
4 | export abstract class FileType {
5 | abstract type: FileTypeEnum;
6 |
7 | abstract validator: V;
8 |
9 | // eslint-disable-next-line no-unused-vars
10 | abstract parse(content: string): ContentParseResult>;
11 | }
12 |
--------------------------------------------------------------------------------
/src/renderer/lib/proxy-interface/rulesFetcher.js:
--------------------------------------------------------------------------------
1 | import { getState } from "../../actions/stateManagement";
2 |
3 | class RulesDataSource {
4 | getRules = async (requestHeaders) => {
5 | let rules = getState("rulesCache") || [];
6 | return rules;
7 | };
8 |
9 | getGroups = async (requestHeaders) => {
10 | let groups = getState("groupsCache") || [];
11 | return groups;
12 | };
13 | }
14 |
15 | export default RulesDataSource;
16 |
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/http/__init__.py:
--------------------------------------------------------------------------------
1 | # This module _must_ trigger the import for http.client. We need to ensure we preload
2 | # the real http module (or import http & import http.server don't work properly), but
3 | # if we do that before importing the client, the client can never be intercepted.
4 |
5 | # There might be a cleaner alternative, but for now we just aggressively pre-intercept
6 | # the client instead.
7 |
8 | from . import client
--------------------------------------------------------------------------------
/src/renderer/utils/storage/index.js:
--------------------------------------------------------------------------------
1 | export const filterSuperObjectByType = (superObject, requestedObjectType) => {
2 | let records = [];
3 |
4 | for (let key in superObject) {
5 | records.push(superObject[key]);
6 | }
7 |
8 | if (!requestedObjectType) {
9 | return records;
10 | }
11 |
12 | return records.filter((record) => {
13 | let objectType = record?.objectType;
14 | return objectType === requestedObjectType;
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/.erb/scripts/check-node-env.js:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 |
3 | export default function checkNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
12 | )
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/renderer/actions/getNextAvailablePort.js:
--------------------------------------------------------------------------------
1 | var portfinder = require("portfinder");
2 | import * as Sentry from "@sentry/browser";
3 |
4 | const getNextAvailablePort = async (port) => {
5 | portfinder.basePort = port;
6 | let availPort;
7 | try {
8 | availPort = await portfinder.getPortPromise();
9 | } catch (error) {
10 | Sentry.captureException(error);
11 | return false;
12 | }
13 | return availPort;
14 | };
15 |
16 | export default getNextAvailablePort;
17 |
--------------------------------------------------------------------------------
/.erb/scripts/clean.js:
--------------------------------------------------------------------------------
1 | import rimraf from "rimraf";
2 | import webpackPaths from "../configs/webpack.paths.ts";
3 | import process from "process";
4 |
5 | const args = process.argv.slice(2);
6 | const commandMap = {
7 | dist: webpackPaths.distPath,
8 | release: webpackPaths.releasePath,
9 | dll: webpackPaths.dllPath,
10 | };
11 |
12 | args.forEach((x) => {
13 | const pathToRemove = commandMap[x];
14 | if (pathToRemove !== undefined) {
15 | rimraf.sync(pathToRemove);
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/.erb/scripts/check-port-in-use.js:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import detectPort from "detect-port";
3 |
4 | const port = process.env.PORT || "1212";
5 |
6 | detectPort(port, (err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`
11 | )
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/build
23 | .erb/dll
24 |
25 | .idea
26 | npm-debug.log.*
27 | *.css.d.ts
28 | *.sass.d.ts
29 | *.scss.d.ts
30 |
--------------------------------------------------------------------------------
/release/app/static/overrides/path/node:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | # Exclude ourselves from PATH, find the real node, then reset PATH
5 | PATH="${PATH//`dirname "$0"`:/}"
6 | real_node=`command -v node`
7 | PATH="`dirname "$0"`:$PATH"
8 |
9 | PREPEND_PATH=`dirname "$0"`/../js/prepend-node.js
10 |
11 | # Call node with the given arguments, prefixed with our extra logic
12 | if command -v winpty >/dev/null 2>&1; then
13 | winpty "$real_node" -r "$PREPEND_PATH" "$@"
14 | else
15 | "$real_node" -r "$PREPEND_PATH" "$@"
16 | fi
--------------------------------------------------------------------------------
/src/renderer/utils/cert/generateSPKIFingerprint.js:
--------------------------------------------------------------------------------
1 | import * as forge from "node-forge";
2 |
3 | const {
4 | pki,
5 | md,
6 | util: { encode64 },
7 | } = forge;
8 |
9 | const generateSPKIFingerprint = (certPem) => {
10 | let cert = pki.certificateFromPem(certPem.toString("utf8"));
11 | return encode64(
12 | pki.getPublicKeyFingerprint(cert.publicKey, {
13 | type: "SubjectPublicKeyInfo",
14 | md: md.sha256.create(),
15 | encoding: "binary",
16 | })
17 | );
18 | };
19 |
20 | export default generateSPKIFingerprint;
21 |
--------------------------------------------------------------------------------
/src/renderer/lib/proxy-interface/userPreferenceFetcher.ts:
--------------------------------------------------------------------------------
1 | import { STORE_NAME } from "lib/storage/constants";
2 | import { UserPreferenceObj } from "lib/storage/types/user-preference";
3 | import storageCacheService from "renderer/services/storage-cache";
4 | import BaseConfigFetcher from "./base";
5 |
6 | class UserPreferenceFetcher implements BaseConfigFetcher {
7 | getConfig = (): UserPreferenceObj => {
8 | return storageCacheService.getCache(STORE_NAME.USER_PREFERENCE) as UserPreferenceObj
9 | };
10 | }
11 |
12 | export default UserPreferenceFetcher
13 |
--------------------------------------------------------------------------------
/src/renderer/lib/proxy-interface/ssl-proxying-config-fetcher.ts:
--------------------------------------------------------------------------------
1 | import { STORE_NAME } from "lib/storage/constants";
2 | import { SSLProxyingJsonObj } from "lib/storage/types/ssl-proxying";
3 | import storageCacheService from "renderer/services/storage-cache";
4 | import BaseConfigFetcher from "./base";
5 |
6 | class SSLProxyingConfigFetcher implements BaseConfigFetcher {
7 | getConfig = (): SSLProxyingJsonObj => {
8 | return storageCacheService.getCache(STORE_NAME.SSL_PROXYING) as SSLProxyingJsonObj
9 | };
10 | }
11 |
12 | export default SSLProxyingConfigFetcher;
13 |
--------------------------------------------------------------------------------
/src/renderer/actions/proxy/getProxyConfig.ts:
--------------------------------------------------------------------------------
1 | import { ip } from "address";
2 | import { staticConfig } from "../../config";
3 | import { getCurrentProxyPort } from "../storage/cacheUtils";
4 |
5 | interface ProxyConfig {
6 | ip: string;
7 | port: number | null;
8 | rootCertPath: string;
9 | }
10 |
11 | const getProxyConfig = (): ProxyConfig | null => {
12 | const proxyIp = ip()!;
13 |
14 | return {
15 | ip: proxyIp,
16 | port: getCurrentProxyPort(),
17 | rootCertPath: staticConfig.ROOT_CERT_PATH,
18 | };
19 | };
20 |
21 | export default getProxyConfig;
22 |
--------------------------------------------------------------------------------
/src/main/preload-apis/AppState.js:
--------------------------------------------------------------------------------
1 | /* UTILS */
2 | const IPC = require("./IPC");
3 |
4 | // Invoke a function in main process to get a state value
5 | const getState = async (stateName) => {
6 | return IPC.invokeEventInMain("get-state", stateName);
7 | };
8 |
9 | // Invoke a function in main process to set a state value
10 | const setState = async (stateName, newValue) => {
11 | return IPC.invokeEventInMain("set-state", {
12 | stateName,
13 | newValue,
14 | });
15 | };
16 |
17 | const STATE_MANAGEMENT = { getState, setState };
18 |
19 | module.exports = STATE_MANAGEMENT;
20 |
--------------------------------------------------------------------------------
/src/lib/storage/constants/index.ts:
--------------------------------------------------------------------------------
1 | let app;
2 | try {
3 | app = require("@electron/remote").app; // for when running in the renderer process
4 | } catch (error) {
5 | app = require("electron").app; // for when running in the main process
6 | }
7 |
8 | export enum STORE_NAME {
9 | SSL_PROXYING = "SSLProxying",
10 | USER_PREFERENCE = "UserPreference",
11 | ACCESSED_FILES = "RecentlyAccessedFiles",
12 | }
13 |
14 | export const DEFAULT_PROXY_PORT = 8281
15 | export const DEFAULT_LOCAL_FILE_LOG_CONFIG = {
16 | isEnabled: false,
17 | storePath: app.getPath("userData"),
18 | filter: [],
19 | }
20 |
--------------------------------------------------------------------------------
/src/renderer/actions/stateManagement/index.js:
--------------------------------------------------------------------------------
1 | /** Do not init any state using this setState since init requires getter & setter */
2 |
3 | // Returns the value of state or null if it doenst exist
4 | export const getState = (stateName) => {
5 | if (global[stateName] !== undefined) {
6 | return global[stateName];
7 | } else return null;
8 | };
9 |
10 | // Sets the value of state or return null if it doenst exist
11 | export const setState = (stateName, newValue) => {
12 | if (global[stateName] !== undefined) {
13 | global[stateName] = newValue;
14 | return true;
15 | } else return null;
16 | };
17 |
--------------------------------------------------------------------------------
/src/lib/storage/action-processors/base.ts:
--------------------------------------------------------------------------------
1 | import StoreWrapper from "../store-wrapper";
2 | import { StorageAction } from "../types/storage-action";
3 |
4 | class BaseActionProcessor {
5 | store: StoreWrapper;
6 |
7 | constructor(store: StoreWrapper) {
8 | if (!store) {
9 | // throw Error;
10 | console.log("No store given");
11 | }
12 |
13 | this.store = store;
14 | }
15 |
16 | process = ({ type, payload }: StorageAction) => {
17 | // Handle all the different actions here
18 | console.log(type);
19 | console.log(payload);
20 | };
21 | }
22 |
23 | export default BaseActionProcessor;
24 |
--------------------------------------------------------------------------------
/assets/requirement.rqset:
--------------------------------------------------------------------------------
1 | designated => (
2 | (
3 | certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = B7SH28MF39
4 | )
5 | or
6 | (
7 | certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = AFBT5Z9V94
8 | )
9 | or
10 | (
11 | certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = YQ5FZQ855D
12 | )
13 | )
14 |
--------------------------------------------------------------------------------
/src/renderer/actions/storage/primaryStorage/index.js:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from "electron";
2 |
3 | export const getStorageSuperObject = () => {
4 | return ipcRenderer.invoke("get-storage-super-object");
5 | };
6 |
7 | export const getStorageObject = (key) => {
8 | return ipcRenderer.invoke("get-storage-object", key);
9 | };
10 |
11 | export const setStorageObject = (object) => {
12 | return ipcRenderer.invoke("set-storage-object", object);
13 | };
14 |
15 | export const deleteItem = (key) => {
16 | return ipcRenderer.invoke("delete-item", key);
17 | };
18 |
19 | export const clearStorage = () => {
20 | return ipcRenderer.invoke("clear-storage");
21 | };
22 |
--------------------------------------------------------------------------------
/src/lib/storage/schemas/userPreferenceSchema.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_PROXY_PORT, DEFAULT_LOCAL_FILE_LOG_CONFIG } from "../constants";
2 |
3 | export const userPreferenceSchema = {
4 | defaultPort: {
5 | type: "number",
6 | default: DEFAULT_PROXY_PORT
7 | },
8 |
9 | localFileLogConfig: {
10 | type: "object",
11 | properties: {
12 | isEnabled: {
13 | type: "boolean",
14 | },
15 | storePath: {
16 | type: "string",
17 | },
18 | filter: {
19 | type: "array",
20 | items: {
21 | type: "string",
22 | },
23 | },
24 | },
25 | default: DEFAULT_LOCAL_FILE_LOG_CONFIG,
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/actions/stateManagement.js:
--------------------------------------------------------------------------------
1 | // Returns the value of state or null if it doesn't exist
2 | export const getState = async (event, payload) => {
3 | const stateName = payload;
4 | if (global[stateName] !== undefined) {
5 | return Promise.resolve(global[stateName]);
6 | }
7 | return Promise.resolve(null);
8 | };
9 |
10 | // Sets the value of state or return null if it doesn't exist
11 | export const setState = async (event, payload) => {
12 | const { stateName, newValue } = payload;
13 | if (global[stateName] !== undefined) {
14 | global[stateName] = newValue;
15 | return Promise.resolve(newValue);
16 | }
17 | return Promise.resolve(null);
18 | };
19 |
--------------------------------------------------------------------------------
/src/utils/sentryInit.js:
--------------------------------------------------------------------------------
1 | const preferenceManager = require("../renderer/utils/userPreferencesManager");
2 | const isMain = process.type === "browser";
3 |
4 | const { init } = isMain
5 | ? require("@sentry/electron/main")
6 | : require("@sentry/electron/renderer");
7 |
8 | let isErrorTrackingEnabled;
9 |
10 | try {
11 | isErrorTrackingEnabled = preferenceManager.default.getPreferences().sentry
12 | .error_tracking_enabled;
13 | } catch (error) {
14 | isErrorTrackingEnabled = true;
15 | }
16 |
17 | isErrorTrackingEnabled
18 | ? init({
19 | dsn:
20 | "https://47cb67fb392b46e0b0fa6607f7fa204b@o407023.ingest.sentry.io/5275504",
21 | })
22 | : null;
23 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-files/actions.ts:
--------------------------------------------------------------------------------
1 | /* Define all methods invokable from webapp here, dummy for now */
2 | export function test(serializableData: any, secodArg: any) {
3 | return new Promise((resolve) => {
4 | console.log("test", serializableData);
5 | console.log("second arg", secodArg);
6 | const result = Math.floor(Math.random() * 100);
7 | console.log("Random result: ", result);
8 | resolve(`Test result: ${result}`);
9 | });
10 | }
11 |
12 | export function magic(anything: any) {
13 | return new Promise((resolve) => {
14 | setTimeout(() => {
15 | console.log("Magic: ", anything);
16 | resolve("Magic done");
17 | }, 2000);
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/src/renderer/types/index.ts:
--------------------------------------------------------------------------------
1 | import { SSLProxyingJsonObj } from "lib/storage/types/ssl-proxying";
2 | import { UserPreferenceObj } from "lib/storage/types/user-preference";
3 |
4 | declare global {
5 | var rq: RQBgGlobalNamespace;
6 | }
7 |
8 | interface RQBgGlobalNamespace {
9 | // cache of main storge
10 | sslProxyingStorage?: SSLProxyingJsonObj;
11 | sslTunnelingSocketsMap?: SSLTunnelingSocketsMap;
12 | userPreferences?: UserPreferenceObj;
13 |
14 | // local cache for background process
15 | proxyServerStatus?: ProxyServerObject
16 | }
17 |
18 | interface ProxyServerObject {
19 | port: number
20 | }
21 |
22 | interface SSLTunnelingSocketsMap {
23 | [domain: string]: any;
24 | }
25 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 30
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pr
8 | - discussion
9 | - e2e
10 | - enhancement
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/src/main/preload-apis/DesktopStorageService.js:
--------------------------------------------------------------------------------
1 | const IPC = require("./IPC");
2 |
3 | class DesktopStorageService {
4 | getStorageSuperObject = () => {
5 | return IPC.invokeEventInMain("get-storage-super-object");
6 | };
7 |
8 | getStorageObject = (key) => {
9 | return IPC.invokeEventInMain("get-storage-object", key);
10 | };
11 |
12 | setStorageObject = (object) => {
13 | return IPC.invokeEventInMain("set-storage-object", object);
14 | };
15 |
16 | deleteItem = (key) => {
17 | return IPC.invokeEventInMain("delete-item", key);
18 | };
19 |
20 | clearStorage = () => {
21 | return IPC.invokeEventInMain("clear-storage");
22 | };
23 | }
24 |
25 | module.exports = DesktopStorageService;
26 |
--------------------------------------------------------------------------------
/release/app/static/overrides/path/prepend.php:
--------------------------------------------------------------------------------
1 | array('proxy' => str_replace('http://', '', getenv('HTTPS_PROXY')))
5 | )
6 | );
7 |
8 | // We've overridden php.ini to ensure this runs. We should go back to
9 | // php.ini, check if there was a previous value, and ensure that
10 | // gets run as well, to make sure we don't break anything.
11 | $phpIniLocation = php_ini_loaded_file();
12 | if ($phpIniLocation) {
13 | $phpIniContents = parse_ini_file($phpIniLocation);
14 | if ($phpIniContents['auto_prepend_file']) {
15 | require($phpIniContents['auto_prepend_file']);
16 | }
17 | }
18 | ?>
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/httptoolkit_intercept.py:
--------------------------------------------------------------------------------
1 | def preload_real_module(*module_names):
2 | # Re-importing the real module at the top level of an override fails after deleting it from
3 | # sys.modules['httplib'] in Python 2. Some interesting issues there ofc, but doing this
4 | # instead works nicely in both Python 2 & 3.
5 | import sys, os
6 |
7 | override_path = os.path.dirname(os.path.abspath(__file__))
8 | original_sys_path = list(sys.path)
9 | sys.path = [p for p in sys.path if p != override_path and p != '']
10 |
11 | for mod in module_names:
12 | if mod in sys.modules:
13 | del sys.modules[mod]
14 | __import__(mod)
15 |
16 | sys.path = original_sys_path
--------------------------------------------------------------------------------
/src/renderer/actions/local-files/sample/base.ts:
--------------------------------------------------------------------------------
1 | import { RPCServiceOverIPC } from "renderer/lib/RPCServiceOverIPC";
2 |
3 | export class TestClass {
4 | constructor(
5 | readonly base: number,
6 | // readonly emitter: (event: any)=> void
7 | readonly emitter: typeof RPCServiceOverIPC.prototype.sendServiceEvent
8 | ) {
9 | this.base = base;
10 | console.log("DBG: Event emitter set");
11 | setInterval(() => {
12 | emitter(`${this.base}: time${Date.now()}`);
13 | }, 3000);
14 | }
15 |
16 | async add(x: number) {
17 | console.log("DBG: add called with x having type", x);
18 | return this.base + x;
19 | }
20 |
21 | async multiply(x: number) {
22 | return this.base * x;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/release/app/static/overrides/path/node.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | SETLOCAL
3 |
4 | REM Exclude ourselves from PATH within this script, to avoid recursing
5 | set ORIGINALPATH=%PATH%
6 | REM Get the current file's folder
7 | set THIS_PATH=%~dp0
8 | REM Strip the trailing slash from the folder
9 | set WRAPPER_FOLDER=%THIS_PATH:~0,-1%
10 | REM Remove that folder from PATH
11 | call set PATH=%%PATH:%WRAPPER_FOLDER%;=%%
12 |
13 | REM Get the real node path, store it in %REAL_NODE%
14 | FOR /F "tokens=*" %%g IN ('where node') do (SET REAL_NODE=%%g)
15 |
16 | REM Reset PATH, so its visible to node & subprocesses
17 | set PATH=%ORIGINALPATH%
18 |
19 | REM Start Node for real, with an extra arg to inject our logic
20 | "%REAL_NODE%" -r "%WRAPPER_FOLDER%\..\js\prepend-node.js" %*
--------------------------------------------------------------------------------
/src/renderer/actions/storage/cacheUtils.ts:
--------------------------------------------------------------------------------
1 | // todo: move to some appropriate location
2 | import UserPreferenceFetcher from "renderer/lib/proxy-interface/userPreferenceFetcher";
3 |
4 | export const getCurrentProxyPort = () => {
5 | return global.rq.proxyServerStatus?.port || null
6 | }
7 |
8 | export const getDefaultProxyPort = () => {
9 | // Load user preferences
10 | const userPreferences = new UserPreferenceFetcher();
11 | return userPreferences.getConfig().defaultPort;
12 | }
13 |
14 | export const getLocalFileLogConfig = () => {
15 | const userPreferences = new UserPreferenceFetcher();
16 | const { localFileLogConfig }= userPreferences.getConfig()
17 | if (!localFileLogConfig) {
18 | return null;
19 | }
20 | return localFileLogConfig;
21 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | # Build Files
22 | .webpack
23 |
24 | release/app/dist
25 | release/build
26 | release/build-setapp
27 | assets/setappPublicKey.pem
28 | .erb/dll
29 |
30 | .idea
31 | npm-debug.log.*
32 | *.css.d.ts
33 | *.sass.d.ts
34 | *.scss.d.ts
35 |
36 | .erb/dll/*
37 | .erb/dll/renderer.dev.dll.js
38 | .erb/dll/renderer.json
39 | components/desktop-app/.erb/dll/renderer.dev.dll.js
40 | components/desktop-app/.erb/dll/renderer.json
41 |
--------------------------------------------------------------------------------
/release/app/static/overrides/path/php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | # Exclude ourselves from PATH, find the real php, then reset PATH
5 | PATH="${PATH//`dirname "$0"`:/}"
6 | real_php=`command -v php`
7 | PATH="`dirname "$0"`:$PATH"
8 |
9 | # Call PHP with the given arguments, and a few extra
10 | PHP_ARGS=(
11 | # Make OpenSSL trust us
12 | -d "openssl.cafile=$SSL_CERT_FILE" \
13 | # Make cURL trust us
14 | -d "curl.cainfo=$SSL_CERT_FILE" \
15 | # Prepend a script that enables the proxy
16 | -d "auto_prepend_file=`dirname "$0"`/prepend.php" \
17 | # Pass through all other provided arguments
18 | "$@"
19 | )
20 |
21 | if command -v winpty >/dev/null 2>&1; then
22 | winpty "$real_php" "${PHP_ARGS[@]}"
23 | else
24 | "$real_php" "${PHP_ARGS[@]}"
25 | fi
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/httplib2.py:
--------------------------------------------------------------------------------
1 | from httptoolkit_intercept import preload_real_module
2 |
3 | preload_real_module('httplib2')
4 |
5 | import httplib2, os, functools
6 |
7 | # Re-export all public fields
8 | from httplib2 import *
9 | # Load a few extra notable private fields, for max compatibility
10 | from httplib2 import __file__, __doc__
11 |
12 | _certPath = os.environ['SSL_CERT_FILE']
13 |
14 | # Ensure all connections trust our cert:
15 | _http_init = httplib2.Http.__init__
16 | @functools.wraps(_http_init)
17 | def _new_http_init(self, *k, **kw):
18 | kList = list(k)
19 | if len(kList) > 3:
20 | kList[3] = _certPath
21 | else:
22 | kw['ca_certs'] = _certPath
23 | _http_init(self, *kList, **kw)
24 | httplib2.Http.__init__ = _new_http_init
--------------------------------------------------------------------------------
/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | // TODO: Centralized logs from renderer process too in main process
2 | const logger = {
3 | enabled:
4 | process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true",
5 | getLogger: function (func) {
6 | if (this.enabled) return func;
7 | return () => {};
8 | },
9 | formatMsg(msg, tag = "Default") {
10 | return `[${tag}] ${msg}`;
11 | },
12 | error: function (msg, tag) {
13 | this.getLogger(console.error).call(this, this.formatMsg(msg, tag));
14 | },
15 | info: function (msg, tag) {
16 | this.getLogger(console.info).call(this, this.formatMsg(msg, tag));
17 | },
18 | log: function (msg, tag) {
19 | this.getLogger(console.log).call(this, this.formatMsg(msg, tag));
20 | },
21 | };
22 |
23 | export default logger;
24 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/ca/windows.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require("child_process");
2 |
3 | const installWindowsCert = async (certPath) => {
4 | const command = `certutil -user -addstore Root ${certPath}`;
5 | try {
6 | execSync(command);
7 | console.log(`${command} executed succesfully`);
8 | return true;
9 | } catch (err) {
10 | console.log(err);
11 | return false;
12 | }
13 | };
14 |
15 | const deleteWindowsCert = async (caName) => {
16 | const command = `certutil -delstore -user Root ${caName}`;
17 | try {
18 | execSync(command);
19 | console.log(`${command} executed succesfully`);
20 | return true;
21 | } catch (err) {
22 | console.log(err);
23 | return false;
24 | }
25 | };
26 |
27 | export { installWindowsCert, deleteWindowsCert };
28 |
--------------------------------------------------------------------------------
/release/app/static/overrides/gems/uri/generic.rb:
--------------------------------------------------------------------------------
1 | # Remove this module from LOAD_PATH, so we can load the real one
2 | gem_override_path = File.expand_path('..', __dir__) # parent dir, we're a subfolder
3 | $LOAD_PATH.reject! { |path| File.expand_path(path) == gem_override_path }
4 |
5 | # Load uri/generic, and inject our proxy settings as the default for all requests
6 | require 'uri/generic'
7 | module URI
8 | class Generic
9 | def find_proxy
10 | # Real code for this avoids it in various cases, if some CGI env vars
11 | # are set, or for 127.*.*.* requests. We want to ensure we use it always.
12 | URI.parse(ENV['HTTP_PROXY'])
13 | end
14 | end
15 | end
16 |
17 | # Put this override directory back on LOAD_PATH
18 | $LOAD_PATH.unshift(gem_override_path)
--------------------------------------------------------------------------------
/src/renderer/actions/storage/initPrimaryStorageCache.js:
--------------------------------------------------------------------------------
1 | import * as PrimaryStorageService from "./primaryStorage";
2 | import * as Sentry from "@sentry/browser";
3 |
4 | const initPrimaryStorageCache = () => {
5 | return PrimaryStorageService.getStorageSuperObject()
6 | .then((newVal) => {
7 | let primaryStorageCache = newVal; // Default value
8 | /* Getter & Setter */
9 | Object.defineProperty(global, "primaryStorageCache", {
10 | get() {
11 | return primaryStorageCache;
12 | },
13 | set(value) {
14 | primaryStorageCache = value;
15 | },
16 | });
17 | })
18 | .catch((e) => {
19 | Sentry.captureException(e);
20 | console.error(e.message);
21 | });
22 | };
23 |
24 | export default initPrimaryStorageCache;
25 |
--------------------------------------------------------------------------------
/src/lib/storage/types/storage-action.ts:
--------------------------------------------------------------------------------
1 | import { AccessedFile } from "../action-processors/accessed-files";
2 | import { ACCESSED_FILES, SSL_PROXYING, USER_PREFERENCE } from "./action-types";
3 | import { ISource as SSLSource } from "./ssl-proxying";
4 | import { ISource as UserPrefereceSource } from "./user-preference"
5 |
6 |
7 | export interface SourcePayload {
8 | data: SSLSource | Partial;
9 | }
10 |
11 | export interface SourcesPayload {
12 | data: SSLSource[] | UserPrefereceSource[] | AccessedFile;
13 | }
14 |
15 | export interface DeletePayload {
16 | id: string;
17 | }
18 |
19 | type Payload = SourcePayload & SourcesPayload & DeletePayload;
20 |
21 | export interface StorageAction {
22 | type: SSL_PROXYING | USER_PREFERENCE | ACCESSED_FILES ;
23 | payload?: Payload;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/actions/initPrimaryStorage.js:
--------------------------------------------------------------------------------
1 | const Store = require("electron-store");
2 |
3 | const thisStore = new Store({
4 | name: "primaryStorage",
5 | watch: true,
6 | });
7 |
8 | export const getStorageSuperObject = () => {
9 | return thisStore.store;
10 | };
11 |
12 | export const getStorageObject = (key) => {
13 | return thisStore.get(key);
14 | };
15 |
16 | export const setStorageObject = (object) => {
17 | thisStore.set(object);
18 | };
19 |
20 | export const deleteItem = (key) => {
21 | thisStore.delete(key);
22 | };
23 |
24 | export const clearStorage = () => {
25 | thisStore.clear();
26 | };
27 |
28 | // Update cache in bg process
29 | thisStore.onDidAnyChange((newVal) => {
30 | if (global.backgroundWindow)
31 | global.backgroundWindow.send("primary-storage-updated", newVal);
32 | });
33 |
--------------------------------------------------------------------------------
/src/renderer/actions/shutdown.js:
--------------------------------------------------------------------------------
1 | // UTILS
2 | import { delay } from "../utils/misc";
3 | // Sentry
4 | import * as Sentry from "@sentry/browser";
5 |
6 | const shutdownHandlers = [];
7 |
8 | export function addShutdownHandler(handler) {
9 | shutdownHandlers.push(handler);
10 | }
11 |
12 | export const shutdown = async () => {
13 | console.log("Shutting down...");
14 | const shutdownPromises = Promise.all(
15 | shutdownHandlers.map(async (handler) => {
16 | try {
17 | await handler();
18 | } catch (e) {
19 | Sentry.captureException(e);
20 | console.log(e);
21 | }
22 | })
23 | );
24 |
25 | await Promise.race([
26 | shutdownPromises,
27 | delay(7500), // After 7.5 seconds, we just close anyway, we're done.
28 | ]);
29 | // process.exit(0);
30 | };
31 |
--------------------------------------------------------------------------------
/src/main/actions/cleanup.js:
--------------------------------------------------------------------------------
1 | const { app, ipcMain } = require("electron");
2 |
3 | const cleanup = () => {
4 | if(global.backgroundProcessStarted) {
5 | if (global.backgroundWindow) {
6 | global.backgroundWindow.webContents.send("shutdown");
7 | } else {
8 | // No backgroundWindow. Quit directly without cleanup
9 | app.quit();
10 | }
11 | } else {
12 | app.quit();
13 | }
14 | };
15 |
16 |
17 | export const getReadyToQuitApp = async () => {
18 | return new Promise((resolve) => {
19 | cleanup();
20 |
21 | if (global.backgroundWindow) {
22 | ipcMain.once("shutdown-success", () => {
23 | console.log("shudown sucess");
24 | global.backgroundWindow?.close();
25 | resolve()
26 | });
27 | } else {
28 | resolve();
29 | }
30 | })
31 | };
--------------------------------------------------------------------------------
/release/app/static/overrides/path/php.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | SETLOCAL
3 |
4 | REM Exclude ourselves from PATH within this script, to avoid recursing
5 | set ORIGINALPATH=%PATH%
6 | REM Get the current file's folder
7 | set THIS_PATH=%~dp0
8 | REM Strip the trailing slash from the folder
9 | set WRAPPER_FOLDER=%THIS_PATH:~0,-1%
10 | REM Remove that folder from PATH
11 | call set PATH=%%PATH:%WRAPPER_FOLDER%;=%%
12 |
13 | REM Get the real php path, store it in %REAL_PHP%
14 | FOR /F "tokens=*" %%g IN ('where php') do (SET REAL_PHP=%%g)
15 |
16 | REM Reset PATH, so its visible to php & subprocesses
17 | set PATH=%ORIGINALPATH%
18 |
19 | REM Start PHP for real, with extra args to override certain configs
20 | "%REAL_PHP%" -d "openssl.cafile=%SSL_CERT_FILE%" -d "curl.cainfo=%SSL_CERT_FILE%" -d "auto_prepend_file=%WRAPPER_FOLDER%\prepend.php" %*
--------------------------------------------------------------------------------
/.erb/scripts/check-build-exists.ts:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from "path";
3 | import chalk from "chalk";
4 | import fs from "fs";
5 |
6 | const webpackPaths = require("../configs/webpack.paths");
7 |
8 | const mainPath = path.join(webpackPaths.distMainPath, "main.js");
9 | const rendererPath = path.join(webpackPaths.distRendererPath, "renderer.js");
10 |
11 | if (!fs.existsSync(mainPath)) {
12 | throw new Error(
13 | chalk.whiteBright.bgRed.bold(
14 | 'The main process is not built yet. Build it by running "npm run build:main"'
15 | )
16 | );
17 | }
18 |
19 | if (!fs.existsSync(rendererPath)) {
20 | throw new Error(
21 | chalk.whiteBright.bgRed.bold(
22 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"'
23 | )
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/constants.ts:
--------------------------------------------------------------------------------
1 | import { homedir } from "os";
2 |
3 | export const CORE_CONFIG_FILE_VERSION = 0.1;
4 | export const WORKSPACE_CONFIG_FILE_VERSION = "0.0.3";
5 | export const CONFIG_FILE = "requestly.json";
6 | export const COLLECTION_AUTH_FILE = "auth.json";
7 | export const DESCRIPTION_FILE = "README.md";
8 | export const COLLECTION_VARIABLES_FILE = "vars.json";
9 | export const ENVIRONMENT_VARIABLES_FILE = "env.json";
10 | export const ENVIRONMENT_VARIABLES_FOLDER = "environments";
11 | export const DS_STORE_FILE = ".DS_Store";
12 | export const GLOBAL_ENV_FILE = "global.json";
13 | export const GIT_FOLDER = ".git";
14 | export const GIT_IGNORE_FILE = ".gitignore";
15 |
16 | export const GLOBAL_CONFIG_FOLDER_PATH = `${homedir()}/.config/requestly`;
17 | export const GLOBAL_CONFIG_FILE_NAME = "config.json";
18 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/ca/index.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require("child_process");
2 | import { installOsxCert } from "./osx";
3 | import { installWindowsCert } from "./windows";
4 | import { getCertStatus } from "./utils";
5 |
6 | const installCert = async (certPath) => {
7 | const certStatus = getCertStatus();
8 |
9 | if (certStatus.installed && certStatus.trusted) {
10 | console.log("Certificate Installed Already");
11 | return true;
12 | } else {
13 | switch (process.platform) {
14 | case "darwin":
15 | return installOsxCert(certPath);
16 | case "win32":
17 | return installWindowsCert(certPath);
18 | default:
19 | console.log(
20 | `${process.platform} is not supported for systemwide proxy`
21 | );
22 | return false;
23 | }
24 | }
25 | };
26 |
27 | export { installCert };
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2019",
4 | "module": "CommonJS",
5 | "lib": ["dom", "esnext"],
6 | "declaration": true,
7 | "declarationMap": true,
8 | "jsx": "react",
9 | "strict": true,
10 | "pretty": true,
11 | "sourceMap": true,
12 | "baseUrl": "./src",
13 | /* Additional Checks */
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "noFallthroughCasesInSwitch": true,
17 | /* Module Resolution Options */
18 | "moduleResolution": "node",
19 | "esModuleInterop": true,
20 | "allowSyntheticDefaultImports": true,
21 | "resolveJsonModule": true,
22 | "allowJs": true,
23 | "outDir": "release/app/dist",
24 | "experimentalDecorators": true,
25 | "emitDecoratorMetadata": true
26 | },
27 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/circularQueue.js:
--------------------------------------------------------------------------------
1 | const Queue = function (maxSize) {
2 | this.reset = function () {
3 | this.head = -1;
4 | this.queue = [];
5 | };
6 |
7 | this.reset();
8 | this.maxSize = maxSize || Queue.MAX_SIZE;
9 |
10 | this.increment = function (number) {
11 | return (number + 1) % this.maxSize;
12 | };
13 | };
14 |
15 | Queue.MAX_SIZE = Math.pow(2, 53) - 1;
16 |
17 | Queue.prototype.enQueue = function (record) {
18 | this.head = this.increment(this.head);
19 | this.queue[this.head] = record;
20 | };
21 |
22 | /**
23 | * @param record Record to look for
24 | * @returns Number Position of record in the queue otherwise -1
25 | */
26 | Queue.prototype.getElementIndex = function (record) {
27 | return this.queue.indexOf(record);
28 | };
29 |
30 | Queue.prototype.print = function () {
31 | for (var i = 0; i <= this.head; i++) {
32 | console.log(this.queue[i]);
33 | }
34 | };
35 |
36 | export default Queue;
37 |
--------------------------------------------------------------------------------
/release/app/static/nss/README.md:
--------------------------------------------------------------------------------
1 | # NSS & Certutil
2 |
3 | This folder contains prebuilt binaries of the NSS tools and corresponding libs, including `certutil`. The folder are named to match Node.js's `process.platform` on the corresponding OSs.
4 |
5 | `certutil` is used to preconfigure Firefox profile's certificate database to trust the HTTP Toolkit certificate authority. We attempt to use any existing `certutil` binary in PATH first, and fall back to the bundled binary if it's not available, or mark Firefox as unavailable if neither work. These binaries aren't included in the npm package for size reasons - in that case, you'll need to ensure certutil is available on your system some other way (for example, download the binaries here and put them in your PATH).
6 |
7 | The files here were downloaded directly from https://tor.eff.org/dist/torbrowser/9.0.9/, in the mar-tools-{linux64,mac64,win64}.zip. They're used unmodified, under the Tor license also in this folder.
8 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-files/sample/service.ts:
--------------------------------------------------------------------------------
1 | import { RPCServiceOverIPC } from "renderer/lib/RPCServiceOverIPC";
2 | import { TestClass } from "./base";
3 |
4 | export class TestService extends RPCServiceOverIPC {
5 | instance?: TestClass;
6 |
7 | constructor() {
8 | super("test"); // namespace for this service
9 | // const instance = new TestClass(base);
10 | // this.instance = instance;
11 | this.init();
12 | }
13 |
14 | init() {
15 | console.log("DBG: exposed build method");
16 | this.exposeMethodOverIPC("build", this.build.bind(this));
17 | }
18 |
19 | async build(base: number) {
20 | console.log("DBG: build called");
21 | this.instance = new TestClass(base, this.sendServiceEvent.bind(this));
22 | this.exposeMethodOverIPC("add", this.instance.add.bind(this.instance));
23 | this.exposeMethodOverIPC(
24 | "multiply",
25 | this.instance.multiply.bind(this.instance)
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/release/app/static/overrides/gems/http.rb:
--------------------------------------------------------------------------------
1 | # Remove this module from LOAD_PATH, so we can load the real one
2 | gem_override_path = File.expand_path(__dir__)
3 | $LOAD_PATH.reject! { |path| File.expand_path(path) == gem_override_path }
4 |
5 | # Load http.rb, and inject our proxy settings as the default for all requests
6 | require 'http'
7 | module HTTP
8 | module RequestHttpToolkitExtensions
9 | def initialize(opts)
10 | if not opts[:proxy] or opts[:proxy].keys.size < 2
11 | proxy_from_env = URI(opts.fetch(:uri).to_s).find_proxy
12 | opts[:proxy] = {
13 | proxy_address: proxy_from_env.host,
14 | proxy_port: proxy_from_env.port
15 | }
16 | end
17 | super(opts)
18 | end
19 | end
20 |
21 | class Request
22 | prepend RequestHttpToolkitExtensions
23 | end
24 | end
25 |
26 | # Put this override directory back on LOAD_PATH
27 | $LOAD_PATH.unshift(gem_override_path)
--------------------------------------------------------------------------------
/src/renderer/actions/storage/initRulesCache.js:
--------------------------------------------------------------------------------
1 | import * as PrimaryStorageService from "./primaryStorage";
2 | /** UTILS */
3 | import { filterSuperObjectByType } from "../../utils/storage";
4 | /** CONSTANTS */
5 | import { CONSTANTS as GLOBAL_CONSTANTS } from "@requestly/requestly-core";
6 | // SENTRY
7 | import * as Sentry from "@sentry/browser";
8 |
9 | const initRulesCache = () => {
10 | return PrimaryStorageService.getStorageSuperObject()
11 | .then((allRecords) => {
12 | let rulesCache = filterSuperObjectByType(
13 | allRecords,
14 | GLOBAL_CONSTANTS.OBJECT_TYPES.RULE
15 | ); // Default value
16 |
17 | /* Getter & Setter */
18 | Object.defineProperty(global, "rulesCache", {
19 | get() {
20 | return rulesCache;
21 | },
22 | set(value) {
23 | rulesCache = value;
24 | },
25 | });
26 | })
27 | .catch((e) => {
28 | Sentry.captureException(e);
29 | console.error(e.message);
30 | });
31 | };
32 |
33 | export default initRulesCache;
34 |
--------------------------------------------------------------------------------
/src/renderer/actions/storage/initGroupsCache.js:
--------------------------------------------------------------------------------
1 | import * as PrimaryStorageService from "./primaryStorage";
2 | /** UTILS */
3 | import { filterSuperObjectByType } from "../../utils/storage";
4 | /** CONSTANTS */
5 | import { CONSTANTS as GLOBAL_CONSTANTS } from "@requestly/requestly-core";
6 | // SENTRY
7 | import * as Sentry from "@sentry/browser";
8 |
9 | const initGroupsCache = () => {
10 | return PrimaryStorageService.getStorageSuperObject()
11 | .then((allRecords) => {
12 | let groupsCache = filterSuperObjectByType(
13 | allRecords,
14 | GLOBAL_CONSTANTS.OBJECT_TYPES.GROUP
15 | ); // Default value
16 |
17 | /* Getter & Setter */
18 | Object.defineProperty(global, "groupsCache", {
19 | get() {
20 | return groupsCache;
21 | },
22 | set(value) {
23 | groupsCache = value;
24 | },
25 | });
26 | })
27 | .catch((e) => {
28 | Sentry.captureException(e);
29 | console.error(e.message);
30 | });
31 | };
32 |
33 | export default initGroupsCache;
34 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/system-wide.js:
--------------------------------------------------------------------------------
1 | import * as _ from "lodash";
2 | import { installCert } from "./ca";
3 |
4 | import { applyProxy, removeProxy } from "./proxy";
5 |
6 | export class SystemWideProxy {
7 | constructor(config) {
8 | this.config = config;
9 | this.id = "system-wide";
10 | this.version = "1.0.0";
11 | this.is_active = false;
12 | }
13 |
14 | async isActive() {
15 | return this.is_active;
16 | }
17 |
18 | async isActivable() {
19 | if (process.platform === "darwin" || process.platform === "win32") {
20 | return true;
21 | }
22 |
23 | return false;
24 | }
25 |
26 | async activate(proxyPort) {
27 | const cert_status = await installCert(this.config.https.certPath);
28 | if (!cert_status) {
29 | throw new Error(`Certificate not installed`);
30 | }
31 |
32 | applyProxy(proxyPort);
33 | this.is_active = true;
34 | }
35 |
36 | async deactivate() {
37 | removeProxy();
38 | this.is_active = false;
39 | }
40 |
41 | async deactivateAll() {
42 | this.deactivate();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fsIgnore-manager.ts:
--------------------------------------------------------------------------------
1 | import ignore from "ignore";
2 | import { getNormalizedPath } from "./common-utils";
3 | import { Config } from "./schemas";
4 | import { Static } from "@sinclair/typebox";
5 |
6 | export class FsIgnoreManager {
7 | private rootPath: string;
8 |
9 | private ig: ReturnType;
10 |
11 | private exclude: string[] = [];
12 |
13 | constructor(rootpath: string, config: Static) {
14 | this.rootPath = getNormalizedPath(rootpath);
15 | this.ig = ignore();
16 | this.loadFsIgnore(config);
17 | }
18 |
19 | private loadFsIgnore(config: Static) {
20 | try {
21 | this.exclude = config.exclude || [];
22 | this.ig.add(this.exclude);
23 | } catch (e: any) {
24 | throw new Error(`Error reading config file: ${e.message}`);
25 | }
26 | }
27 |
28 | public checkShouldIgnore(path: string): boolean {
29 | // Convert absolute path to relative path
30 | const relativePath = path.replace(this.rootPath, "");
31 | const isIgnored = this.ig.ignores(relativePath);
32 | return isIgnored;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.erb/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | const { notarize } = require("@electron/notarize");
2 | const { build } = require("../../package.json");
3 |
4 | // NO LONGER USED
5 |
6 | exports.default = async function notarizeMacos(context) {
7 | const { electronPlatformName, appOutDir } = context;
8 | if (electronPlatformName !== "darwin") {
9 | return;
10 | }
11 |
12 | // Allow it as we dont publish using CI
13 | // if (!process.env.CI) {
14 | // console.warn('Skipping notarizing step. Packaging is not running in CI');
15 | // return;
16 | // }
17 |
18 | if (!("APPLE_ID" in process.env && "APPLE_ID_PASS" in process.env)) {
19 | console.warn(
20 | "Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set"
21 | );
22 | return;
23 | }
24 |
25 | const appName = context.packager.appInfo.productFilename;
26 |
27 | await notarize({
28 | tool: "notarytool",
29 | appBundleId: build.appId,
30 | appPath: `${appOutDir}/${appName}.app`,
31 | appleId: process.env.APPLE_ID,
32 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
33 | teamId: process.env.APPLE_TEAM_ID
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/src/renderer/utils/cert/index.ts:
--------------------------------------------------------------------------------
1 | import * as crypto from "crypto";
2 | import * as forge from "node-forge";
3 |
4 | export const parseCert = forge.pki.certificateFromPem;
5 |
6 | export function getTimeToCertExpiry(cert: forge.pki.Certificate): number {
7 | const expiry = cert.validity.notAfter.valueOf();
8 | return expiry - Date.now();
9 | }
10 |
11 | // A series of magic incantations that matches the behaviour of openssl's
12 | // -subject_hash_old output, as expected by Android's cert store.
13 | export function getCertificateSubjectHash(cert: forge.pki.Certificate) {
14 | const derBytes = forge.asn1
15 | .toDer((forge.pki as any).distinguishedNameToAsn1(cert.subject))
16 | .getBytes();
17 |
18 | return crypto
19 | .createHash("md5")
20 | .update(derBytes)
21 | .digest()
22 | .readUInt32LE(0)
23 | .toString(16);
24 | }
25 |
26 | // Get a full SHA1 hash of the certificate
27 | export function getCertificateFingerprint(cert: forge.pki.Certificate) {
28 | return forge.md.sha1
29 | .create()
30 | .update(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes())
31 | .digest()
32 | .toHex();
33 | }
34 |
--------------------------------------------------------------------------------
/.erb/scripts/electron-rebuild.js:
--------------------------------------------------------------------------------
1 | import { execSync } from "child_process";
2 | import fs, {readdirSync} from "fs";
3 | import { dependencies } from "../../release/app/package.json";
4 | import webpackPaths from "../configs/webpack.paths";
5 |
6 | const getModules = (source, exclude) =>
7 | readdirSync(source, { withFileTypes: true })
8 | .filter(dirent => dirent.isDirectory() && !exclude.includes(dirent.name))
9 | .map(dirent => dirent.name);
10 |
11 | if (
12 | Object.keys(dependencies || {}).length > 0 &&
13 | fs.existsSync(webpackPaths.appNodeModulesPath)
14 | ) {
15 | // Skip modules that are already prebuilt
16 | const exclude = ['win-version-info'];
17 | const modules = getModules(webpackPaths.appNodeModulesPath, exclude);
18 |
19 | const electronRebuildCmd = `../../node_modules/.bin/electron-rebuild --parallel --types prod,dev,optional --only ${modules.toString()} --module-dir .`;
20 | const cmd =
21 | process.platform === "win32"
22 | ? electronRebuildCmd.replace(/\//g, "\\")
23 | : electronRebuildCmd;
24 | execSync(cmd, {
25 | cwd: webpackPaths.appPath,
26 | stdio: "inherit",
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/mobile/utils.ts:
--------------------------------------------------------------------------------
1 | import Adb from "@devicefarmer/adbkit";
2 |
3 | export const getAvailableAndroidDevices = async () => {
4 | const adbClient = Adb.createClient();
5 | const devices = await adbClient.listDevicesWithPaths();
6 | console.log("Available Android Devices", { devices });
7 | return devices;
8 | };
9 |
10 | export const getAvailableIOSDevices = () => {};
11 |
12 | export function delay(durationMs: number): Promise {
13 | // eslint-disable-next-line no-promise-executor-return
14 | return new Promise((resolve) => setTimeout(resolve, durationMs));
15 | }
16 |
17 | // export async function waitUntil(
18 | // delayMs: number,
19 | // tries: number,
20 | // test: () => Promise
21 | // ): Promise> {
22 | // let result = tries > 0 && (await test());
23 |
24 | // while (tries > 0 && !result) {
25 | // // eslint-disable-next-line no-param-reassign
26 | // tries -= 1;
27 | // await delay(delayMs);
28 | // result = await test();
29 | // }
30 |
31 | // if (!result) throw new Error(`Wait loop failed`);
32 | // else return result as Exclude;
33 | // }
34 |
--------------------------------------------------------------------------------
/src/renderer/index.js:
--------------------------------------------------------------------------------
1 |
2 | const initGlobalNamespace = () => {
3 | global.rq = global.rq || {};
4 | };
5 |
6 | initGlobalNamespace();
7 |
8 | // ACTIONS
9 | import initEventHandlers from "./actions/initEventHandlers";
10 | // import initPrimaryStorageCache from "./actions/storage/initPrimaryStorageCache";
11 | import initRulesCache from "./actions/storage/initRulesCache";
12 | import initGroupsCache from "./actions/storage/initGroupsCache";
13 | import { initAppManager } from "./actions/apps";
14 | import "./types";
15 | import { FsManagerBuilderRPCService } from "./actions/local-sync/fs-manager-builder.rpc-service";
16 | import { clearStoredLogs } from "./lib/proxy-interface/loggerService";
17 |
18 | // initPrimaryStorageCache();
19 | initRulesCache();
20 | initGroupsCache();
21 | /** IPC **/
22 | initEventHandlers();
23 | initAppManager();
24 | /* stored logs */
25 | clearStoredLogs();
26 |
27 | // import "../utils/sentryInit";
28 | // const LocalFileSyncer = new LocalFileSync();
29 | // LocalFileSyncer.init();
30 |
31 | // const TestServiceServer = new TestService();
32 |
33 | // eslint-disable-next-line no-unused-vars, no-new
34 | new FsManagerBuilderRPCService();
35 |
--------------------------------------------------------------------------------
/release/app/static/overrides/gems/net/https.rb:
--------------------------------------------------------------------------------
1 | # require 'net/https' is not necessary in modern ruby, as net/http
2 | # can handle HTTPS all by itself. That said, it's still used in
3 | # places, and its use of require_relative to load 'http' means that
4 | # it avoids our net/http hook.
5 |
6 | # When using standard OpenSSL that's not a big problem, because
7 | # we also set SSL_CERT_FILE, which ensures we trust the certificate.
8 | # In some environments though (default Mac Ruby installs) that
9 | # variable is ignored, and so the net/http hook is *necessary*.
10 |
11 | # All this file does is import our hooked HTTP version, before
12 | # running the real module as normal, to guarantee the hook is
13 | # always in place in every case.
14 |
15 | require_relative 'http'
16 |
17 | # Remove this module from LOAD_PATH, so we can load the real one
18 | gem_override_path = File.expand_path('..', __dir__) # parent dir, we're a subfolder
19 | $LOAD_PATH.reject! { |path| File.expand_path(path) == gem_override_path }
20 |
21 | # Load the real net/https module as normal
22 | require 'net/https'
23 |
24 | # Put this override directory back on LOAD_PATH again
25 | $LOAD_PATH.unshift(gem_override_path)
--------------------------------------------------------------------------------
/.erb/configs/webpack.paths.ts:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const rootPath = path.join(__dirname, "../..");
4 |
5 | const dllPath = path.join(__dirname, "../dll");
6 |
7 | const srcPath = path.join(rootPath, "src");
8 | const srcMainPath = path.join(srcPath, "main");
9 | const srcRendererPath = path.join(srcPath, "renderer");
10 |
11 | const releasePath = path.join(rootPath, "release");
12 | const appPath = path.join(releasePath, "app");
13 | const appPackagePath = path.join(appPath, "package.json");
14 | const appNodeModulesPath = path.join(appPath, "node_modules");
15 | const srcNodeModulesPath = path.join(srcPath, "node_modules");
16 |
17 | const distPath = path.join(appPath, "dist");
18 | const distMainPath = path.join(distPath, "main");
19 | const distRendererPath = path.join(distPath, "renderer");
20 |
21 | const buildPath = path.join(releasePath, "build");
22 |
23 | module.exports = {
24 | rootPath,
25 | dllPath,
26 | srcPath,
27 | srcMainPath,
28 | srcRendererPath,
29 | releasePath,
30 | appPath,
31 | appPackagePath,
32 | appNodeModulesPath,
33 | srcNodeModulesPath,
34 | distPath,
35 | distMainPath,
36 | distRendererPath,
37 | buildPath,
38 | };
39 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-files/index.ts:
--------------------------------------------------------------------------------
1 | import { RPCServiceOverIPC } from "../../lib/RPCServiceOverIPC";
2 |
3 | import * as MethodsToExpose from "./actions";
4 |
5 | /* only contains template code right now but can be THE place for files code */
6 |
7 | export class LocalFileSync extends RPCServiceOverIPC {
8 | constructor() {
9 | super("fs"); // namespace for this service
10 | this.init();
11 | }
12 |
13 | init() {
14 | // any initialization logic if needed
15 |
16 | // setup all methods to be exposed over IPC
17 | Object.values(MethodsToExpose).forEach((method) => {
18 | this.exposeMethodOverIPC(method.name, method);
19 | });
20 |
21 | // setup OS event listener however
22 | // setInterval(() => {
23 | // /* GARGBAGE POC CODE FOR NOW */
24 | // const randomMessage = Math.floor(Math.random() * 100);
25 | // console.log(`${Date.now()} - Sending event to webapp: `, randomMessage);
26 |
27 | // // relay those events over to webapp
28 | // this.sendServiceEvent({
29 | // type: "test",
30 | // data: { test: "data - non-changing", message: randomMessage },
31 | // });
32 | // }, 7500);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/renderer/actions/saveRootCert.js:
--------------------------------------------------------------------------------
1 | const { dialog, app } = require("@electron/remote");
2 | const fs = require("fs-extra");
3 | const path = require("path");
4 | //CONFIG
5 | import { staticConfig } from "../config";
6 | // Sentry
7 | import * as Sentry from "@sentry/browser";
8 |
9 | var options = {
10 | title: "Save root certificate",
11 | defaultPath: path.join(app.getPath("desktop"), "RQProxyCA.pem"),
12 | buttonLabel: "Save certificate",
13 |
14 | filters: [{ name: "pem", extensions: ["pem"] }],
15 | properties: ["createDirectory"],
16 | };
17 |
18 | const saveRootCert = async () => {
19 | return dialog
20 | .showSaveDialog(null, options)
21 | .then(async ({ filePath }) => {
22 | return fs
23 | .copy(staticConfig.ROOT_CERT_PATH, filePath)
24 | .then(() => {
25 | return { success: true };
26 | })
27 | .catch((err) => {
28 | Sentry.captureException(err);
29 | return { success: false };
30 | });
31 | })
32 | .catch((e) => {
33 | Sentry.captureException(e);
34 | console.error(e.message);
35 | return { success: false };
36 | });
37 | };
38 |
39 | export default saveRootCert;
40 |
--------------------------------------------------------------------------------
/release/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "requestly",
3 | "productName": "Requestly",
4 | "version": "25.12.15",
5 | "private": true,
6 | "description": "Intercept & Modify HTTP Requests",
7 | "main": "./dist/main/main.js",
8 | "author": {
9 | "name": "BrowserStack Inc.",
10 | "email": "contact@requestly.io",
11 | "url": "https://requestly.io/"
12 | },
13 | "scripts": {
14 | "electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.js",
16 | "preinstall": "npx npm-force-resolutions",
17 | "postinstall": "npm run electron-rebuild && npm run link-modules"
18 | },
19 | "license": "UNLICENSED",
20 | "dependencies": {
21 | "@httptoolkit/browser-launcher": "^2.2.0",
22 | "@httptoolkit/osx-find-executable": "^2.0.1",
23 | "brotli-wasm": "^1.1.0",
24 | "ws": "^8.16.0"
25 | },
26 | "optionalDependencies": {
27 | "@setapp/framework-wrapper": "^4.3.4"
28 | },
29 | "devDependencies": {
30 | "electron": "^27.3.8",
31 | "npm-force-resolutions": "0.0.10"
32 | },
33 | "resolutions": {
34 | "bufferutil": "4.0.3",
35 | "utf-8-validate": "5.0.6"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fs/access-fallback.decorator.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { isAccessError, isThenable } from "../common-utils";
3 |
4 | export function AccessFallback(
5 | fallback: (...args: TArgs) => TReturn
6 | ) {
7 | return function (
8 | target: any,
9 | _propertyKey: string | symbol,
10 | descriptor: TypedPropertyDescriptor<(...args: TArgs) => TReturn>
11 | ) {
12 | const originalMethod = descriptor.value!;
13 |
14 | descriptor.value = function (...args: TArgs): TReturn {
15 | try {
16 | const possiblePromiseResult = originalMethod.apply(target, args);
17 | if (isThenable(possiblePromiseResult)) {
18 | return possiblePromiseResult.catch((e) => {
19 | if (isAccessError(e)) {
20 | return fallback(...args);
21 | }
22 | throw e;
23 | }) as TReturn; // casting manually to let TS know that if originalMethod's return is a promise then so is ours
24 | }
25 | return possiblePromiseResult;
26 | } catch (error: any) {
27 | if (isAccessError(error)) {
28 | return fallback(...args);
29 | }
30 | throw error;
31 | }
32 | };
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.base.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import webpack from "webpack";
6 | import webpackPaths from "./webpack.paths";
7 | import { dependencies as externals } from "../../release/app/package.json";
8 |
9 | export default {
10 | externals: [...Object.keys(externals || {})],
11 |
12 | stats: "errors-only",
13 |
14 | module: {
15 | rules: [
16 | {
17 | test: /\.[jt]sx?$/,
18 | exclude: /node_modules/,
19 | use: {
20 | loader: "ts-loader",
21 | },
22 | },
23 | {
24 | test: /\.node$/,
25 | loader: "node-loader",
26 | },
27 | ],
28 | },
29 |
30 | output: {
31 | path: webpackPaths.srcPath,
32 | // https://github.com/webpack/webpack/issues/1114
33 | library: {
34 | type: "commonjs2",
35 | },
36 | },
37 |
38 | /**
39 | * Determine the array of extensions that should be used to resolve modules.
40 | */
41 | resolve: {
42 | extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],
43 | modules: [webpackPaths.srcPath, "node_modules"],
44 | },
45 |
46 | plugins: [
47 | new webpack.EnvironmentPlugin({
48 | NODE_ENV: "production",
49 | }),
50 | ],
51 | };
52 |
--------------------------------------------------------------------------------
/src/utils/envDetection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check if you're in a render window
3 | * @returns {boolean}
4 | */
5 | export const isThisRenderer = () => {
6 | // running in a web browser
7 | if (typeof process === "undefined") return true;
8 | // node-integration is disabled
9 | if (!process) return true;
10 | // We're in node.js somehow
11 | if (!process.type) return false;
12 |
13 | return process.type === "renderer";
14 | };
15 |
16 | /**
17 | * Check if you're in a Electron window - Any process
18 | * @returns {boolean}
19 | */
20 | export const isThisElectron = () => {
21 | // Renderer process
22 | if (
23 | typeof window !== "undefined" &&
24 | typeof window.process === "object" &&
25 | window.process.type === "renderer"
26 | ) {
27 | return true;
28 | }
29 | // Main process
30 | if (
31 | typeof process !== "undefined" &&
32 | typeof process.versions === "object" &&
33 | !!process.versions.electron
34 | ) {
35 | return true;
36 | }
37 | // Detect the user agent when the `nodeIntegration` option is set to true
38 | if (
39 | typeof navigator === "object" &&
40 | typeof navigator.userAgent === "string" &&
41 | navigator.userAgent.indexOf("Electron") >= 0
42 | ) {
43 | return true;
44 | }
45 |
46 | return false;
47 | };
48 |
--------------------------------------------------------------------------------
/release/app/static/overrides/gems/net/http.rb:
--------------------------------------------------------------------------------
1 | # Remove this module from LOAD_PATH, so we can load the real one
2 | gem_override_path = File.expand_path('..', __dir__) # parent dir, we're a subfolder
3 | $LOAD_PATH.reject! { |path| File.expand_path(path) == gem_override_path }
4 |
5 | # Override net/http, the built-in HTTP module to inject our cert
6 | # This isn't necessary with OpenSSL (doesn't hurt), but LibreSSL ignores
7 | # the SSL_CERT_FILE setting, so we need to be more explicit:
8 | require 'net/http'
9 | module Net
10 | module HTTPHttpToolkitExtensions
11 | def ca_file=(path)
12 | # If you try to use a certificate, use ours instead
13 | super(ENV['SSL_CERT_FILE'])
14 | end
15 |
16 | def cert_store=(store)
17 | # If you try to use a whole store of certs, use ours instead
18 | self.ca_file = ENV['SSL_CERT_FILE']
19 | end
20 |
21 | def use_ssl=(val)
22 | # If you try to use SSL, make sure you trust our cert first
23 | self.ca_file = ENV['SSL_CERT_FILE']
24 | super(val)
25 | end
26 | end
27 |
28 | class HTTP
29 | prepend HTTPHttpToolkitExtensions
30 | end
31 | end
32 |
33 | # Put this override directory back on LOAD_PATH
34 | $LOAD_PATH.unshift(gem_override_path)
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/proxy/index.js:
--------------------------------------------------------------------------------
1 | import { getCurrentProxyPort } from "renderer/actions/storage/cacheUtils";
2 |
3 | let applyProxyOsx, removeProxyOsx, applyProxyWindows, removeProxyWindows;
4 |
5 | if (process.platform === "darwin") {
6 | ({ applyProxyOsx, removeProxyOsx } = require("./osx"));
7 | }
8 | else if (process.platform === "win32") {
9 | ({ applyProxyWindows, removeProxyWindows } = require("./windows"));
10 | }
11 |
12 | const applyProxy = (port) => {
13 | const targetPort = port ? port : getCurrentProxyPort()
14 | const host = "127.0.0.1";
15 |
16 | switch (process.platform) {
17 | case "darwin":
18 | applyProxyOsx(host, targetPort);
19 | break;
20 | case "win32":
21 | applyProxyWindows(host, targetPort);
22 | break;
23 | default:
24 | console.log(`${process.platform} is not supported for systemwide proxy`);
25 | return false;
26 | }
27 | };
28 |
29 | const removeProxy = () => {
30 | switch (process.platform) {
31 | case "darwin":
32 | removeProxyOsx();
33 | break;
34 | case "win32":
35 | removeProxyWindows();
36 | break;
37 | default:
38 | console.log(`${process.platform} is not supported for systemwide proxy`);
39 | return false;
40 | }
41 | };
42 |
43 | export { applyProxy, removeProxy };
44 |
--------------------------------------------------------------------------------
/src/renderer/utils/misc/index.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "@sentry/browser";
2 |
3 | export const delay = (durationMs) => {
4 | return new Promise((resolve) => setTimeout(resolve, durationMs));
5 | };
6 |
7 | export const waitUntil = async (delayMs, tries, test) => {
8 | let result = tries > 0 && (await test());
9 | while (tries > 0 && !result) {
10 | tries = tries - 1;
11 | await delay(delayMs);
12 | result = await test();
13 | }
14 | if (!result) throw new Error(`Wait loop failed`);
15 | else return result;
16 | };
17 |
18 | export const getDeferred = () => {
19 | let resolve = undefined;
20 | let reject = undefined;
21 | let promise = new Promise((resolveCb, rejectCb) => {
22 | resolve = resolveCb;
23 | reject = rejectCb;
24 | });
25 | // TS thinks we're using these before they're assigned, which is why
26 | // we need the undefined types, and the any here.
27 | return { resolve, reject, promise };
28 | };
29 |
30 | // Wait for a promise, falling back to defaultValue on error or timeout
31 | export const returnWithFallback = (promise, timeoutMs, fallbackValue) =>
32 | Promise.race([
33 | promise.catch((err) => {
34 | Sentry.captureException(err);
35 | console.error(err.message);
36 | return fallbackValue;
37 | }),
38 | delay(timeoutMs).then(() => fallbackValue),
39 | ]);
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues. 🐞
4 | labels: "bug"
5 | ---
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 |
12 |
13 | - [ ] Using the latest version of Requestly
14 |
15 | ## Expected Behavior
16 |
17 |
18 |
19 | ## Current Behavior
20 |
21 |
22 |
23 | ## Steps to Reproduce
24 |
25 |
26 |
27 |
28 | 1.
29 |
30 | 2.
31 |
32 | 3.
33 |
34 | 4.
35 |
36 | ## Possible Solution (Not obligatory)
37 |
38 |
39 |
40 | ## Context
41 |
42 |
43 |
44 |
45 |
46 | ## Your Environment
47 |
48 |
49 |
50 | - Node version :
51 | - electron-react-boilerplate version or branch :
52 | - Operating System and version :
53 | - Link to your project :
54 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/decorators/handle-error.decorator.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { isThenable } from "../common-utils";
3 | import { ErrorCode, FileSystemError, FileTypeEnum } from "../types";
4 |
5 | export function HandleError(
6 | _target: any,
7 | _propertyKey: string | symbol,
8 | descriptor: PropertyDescriptor
9 | ) {
10 | const originalMethod = descriptor.value!;
11 |
12 | descriptor.value = function (...args: any[]) {
13 | try {
14 | const possiblePromiseResult = originalMethod.apply(this, args);
15 | if (isThenable(possiblePromiseResult)) {
16 | return possiblePromiseResult.catch((e) => {
17 | return {
18 | type: "error",
19 | error: {
20 | code: ErrorCode.UNKNOWN,
21 | message: e.message || "An unexpected error has occured!",
22 | path: e.path || "Unknown path",
23 | fileType: FileTypeEnum.UNKNOWN,
24 | },
25 | };
26 | });
27 | }
28 | return possiblePromiseResult;
29 | } catch (e: any) {
30 | return {
31 | type: "error",
32 | error: {
33 | code: ErrorCode.UNKNOWN,
34 | message: e.message || "An unexpected error has occured!",
35 | path: e.path || "Unknown path",
36 | fileType: FileTypeEnum.UNKNOWN,
37 | },
38 | };
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["erb"],
3 | rules: {
4 | // A temporary hack related to IDE not resolving correct package.json
5 | "import/no-extraneous-dependencies": "off",
6 | "no-console": "off",
7 | "import/order": "off",
8 | "import/extensions": "off",
9 | "import/prefer-default-export": "off",
10 | "no-await-in-loop": "off",
11 | "prettier/prettier": "off",
12 | "no-nested-ternary": "off",
13 | "no-restricted-syntax": "off",
14 | "no-unused-vars": [
15 | "warn",
16 | {
17 | "argsIgnorePattern": "^_",
18 | "varsIgnorePattern": "^_"
19 | }
20 | ],
21 | },
22 | parserOptions: {
23 | ecmaVersion: 2020,
24 | sourceType: "module",
25 | project: "./tsconfig.json",
26 | tsconfigRootDir: __dirname,
27 | createDefaultProgram: true,
28 | },
29 | settings: {
30 | "import/resolver": {
31 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
32 | node: {
33 | extensions: [".js", ".jsx", ".ts", ".tsx"],
34 | moduleDirectory: ["node_modules", "./src/"],
35 | },
36 | webpack: {
37 | config: require.resolve("./.erb/configs/webpack.config.eslint.ts"),
38 | },
39 | typescript: {},
40 | },
41 | "import/parsers": {
42 | "@typescript-eslint/parser": [".ts", ".tsx"],
43 | },
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/src/lib/storage/store-wrapper.ts:
--------------------------------------------------------------------------------
1 | const Store = require("electron-store");
2 |
3 | class StoreWrapper {
4 | storeName: string;
5 | store: typeof Store;
6 | constructor(storeName: string, schema?: any) {
7 | if (!storeName) {
8 | // throw Error("No store name given");
9 | console.log("No store Name given");
10 | }
11 |
12 | this.storeName = storeName;
13 | const storeProperties = {
14 | name: storeName,
15 | cwd: "storage",
16 | watch: true,
17 | }
18 | if(schema) {
19 | // @ts-ignore
20 | storeProperties.schema = schema
21 | }
22 | this.store = new Store(storeProperties);
23 |
24 | this.initListener();
25 | }
26 |
27 | getAll = () => {
28 | return this.store.store;
29 | };
30 |
31 | get = (key: string) => {
32 | return this.store.get(key);
33 | };
34 |
35 | set = (object: any) => {
36 | this.store.set(object);
37 | };
38 |
39 | delete = (key: string) => {
40 | this.store.delete(key);
41 | };
42 |
43 | clear = () => {
44 | this.store.clear();
45 | };
46 |
47 | initListener = () => {
48 | this.store.onDidAnyChange(() => {
49 | // @ts-ignore
50 | if (global.backgroundWindow)
51 | // @ts-ignore
52 | global.backgroundWindow.send("rq-storage:storage-updated", {
53 | storeName: this.storeName,
54 | });
55 | });
56 | };
57 | }
58 |
59 | export default StoreWrapper;
60 |
--------------------------------------------------------------------------------
/src/main/actions/setupIPCForwarding.js:
--------------------------------------------------------------------------------
1 | import { ipcMain } from "electron";
2 |
3 | export const setupIPCForwardingToBackground = (backgroundWindow) => {
4 | ipcMain.handle(
5 | "forward-event-from-webapp-to-background-and-await-reply",
6 | async (event, incomingData) => {
7 | return new Promise((resolve) => {
8 | const { actualPayload, eventName } = incomingData;
9 | ipcMain.once(`reply-${eventName}`, (responseEvent, responsePayload) => {
10 | resolve(responsePayload);
11 | });
12 | backgroundWindow.webContents.send(eventName, actualPayload);
13 | });
14 | }
15 | );
16 | };
17 |
18 | export const setupIPCForwardingToWebApp = (webAppWindow) => {
19 | ipcMain.handle(
20 | "forward-event-from-background-to-webapp-and-await-reply",
21 | async (event, incomingData) => {
22 | return new Promise((resolve) => {
23 | const { actualPayload, eventName } = incomingData;
24 | ipcMain.once(`reply-${eventName}`, (responseEvent, responsePayload) => {
25 | resolve(responsePayload);
26 | });
27 | webAppWindow.webContents.send(eventName, actualPayload);
28 | });
29 | }
30 | );
31 |
32 | ipcMain.on("send-from-background-to-webapp", (event, incomingData) => {
33 | const { payload, channel } = incomingData;
34 | console.log("Sending to webapp", channel, payload);
35 | webAppWindow.webContents.send(channel, payload);
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/main/preload.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable func-names */
2 | /** Babel */
3 | require("core-js/stable");
4 | require("regenerator-runtime/runtime");
5 | // Core
6 | const { contextBridge } = require("electron");
7 | const { app } = require("@electron/remote");
8 |
9 | const DesktopStorageService = require("./preload-apis/DesktopStorageService");
10 | // Sub
11 | const IPC = require("./preload-apis/IPC");
12 | const STATE_MANAGEMENT = require("./preload-apis/AppState");
13 |
14 | let appVersion = null;
15 | const isSetappBuild = process.env.IS_SETAPP_BUILD === "true";
16 |
17 | if (process.env.NODE_ENV === "development") {
18 | appVersion = require("../../package.json").version;
19 | } else {
20 | appVersion = app.getVersion();
21 | }
22 |
23 | (function (window) {
24 | // Build the RQ object
25 | const RQ = window.RQ || {};
26 | RQ.DESKTOP = RQ.DESKTOP || {};
27 |
28 | // Application Info
29 | RQ.MODE = "DESKTOP";
30 | // TODO @Sachin: TMP VERSION as of now
31 | RQ.DESKTOP.VERSION = appVersion || "1.0";
32 | RQ.DESKTOP.IS_SETAPP_BUILD = isSetappBuild;
33 | // Services
34 | RQ.DESKTOP.SERVICES = RQ.DESKTOP.SERVICES || {};
35 | // Services - Storage Service
36 | RQ.DESKTOP.SERVICES.STORAGE_SERVICE = new DesktopStorageService();
37 | // Services - App State
38 | RQ.DESKTOP.SERVICES.STATE_MANAGEMENT = STATE_MANAGEMENT;
39 | // Services - IPC
40 | RQ.DESKTOP.SERVICES.IPC = IPC;
41 |
42 | // Expose to frontend
43 | contextBridge.exposeInMainWorld("RQ", RQ);
44 | })(window);
45 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/browsers/safari.js:
--------------------------------------------------------------------------------
1 | import { SystemWideProxy } from "../os/system-wide";
2 | import { launchBrowser } from "./browser-handler";
3 |
4 | class SafariInterceptor {
5 | constructor(config, variantName) {
6 | this.config = config;
7 | this.variantName = variantName;
8 | this.activeBrowsers = {};
9 | }
10 |
11 | isActive(proxyPort) {
12 | return false;
13 | }
14 |
15 | async isActivable() {
16 | return process.platform === "darwin";
17 | }
18 |
19 | async activate(proxyPort) {
20 | return new Error("Not supported");
21 | }
22 |
23 | async deactivate(proxyPort) {
24 | return true;
25 | }
26 |
27 | async deactivateAll() {
28 | return true;
29 | }
30 | }
31 |
32 | // Hack for now as instance level safari proxy doesn't work
33 | export class FreshSafari extends SystemWideProxy {
34 | constructor(config) {
35 | super(config, "safari");
36 | this.id = "fresh-safari";
37 | this.version = "1.0.0";
38 | }
39 | async isActivable() {
40 | return process.platform === "darwin";
41 | }
42 |
43 | async activate(proxyPort) {
44 | await super.activate(proxyPort)
45 | const browser = await launchBrowser(
46 | null,
47 | {
48 | browser: "safari",
49 | },
50 | this.config.configPath
51 | );
52 | }
53 | }
54 |
55 | export class ExistingSafari extends SafariInterceptor {
56 | constructor(config) {
57 | super(config, "safari");
58 | this.id = "existing-safari";
59 | this.version = "1.0.0";
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/ca/osx.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require("child_process");
2 |
3 | const installOsxCert = async (certPath) => {
4 | // const command = `security add-trusted-cert \
5 | // -d -r trustRoot \
6 | // -k $HOME/Library/Keychains/login.keychain "${cert_path}"\
7 | // `
8 | // console.log(command);
9 | // sudo.exec(command, {"name": "Requestly"}, (error) => {
10 | // if (error) {
11 | // console.error(error);
12 | // return;
13 | // }
14 | // console.log(`${command} executed`);
15 | // });
16 |
17 | const command = `osascript -e \
18 | 'do shell script \
19 | "security add-trusted-cert \
20 | -r trustRoot \
21 | -k $HOME/Library/Keychains/login.keychain \\"${certPath}\\"\
22 | " with prompt "Requestly wants to store SSL certificate to keychain."'`;
23 |
24 | try {
25 | execSync(command);
26 | console.log(`${command} executed succesfully`);
27 | return true;
28 | } catch (err) {
29 | console.log(err);
30 | return false;
31 | }
32 | };
33 |
34 | const deleteOsxCert = async (caName) => {
35 | const command = `osascript -e \
36 | 'do shell script \
37 | "security delete-certificate -c \\"${caName}\\" $HOME/Library/Keychains/login.keychain \
38 | " with prompt "Requestly wants to remove SSL certificate from keychain."'`;
39 | try {
40 | execSync(command);
41 | console.log(`${command} executed succesfully`);
42 | return true;
43 | } catch (err) {
44 | console.log(err);
45 | return false;
46 | }
47 | };
48 |
49 | export { installOsxCert, deleteOsxCert };
50 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fs-manager-builder.rpc-service.ts:
--------------------------------------------------------------------------------
1 | import { RPCServiceOverIPC } from "renderer/lib/RPCServiceOverIPC";
2 | import {
3 | createWorkspaceFolder,
4 | getAllWorkspaces,
5 | removeWorkspace,
6 | } from "./fs-utils";
7 | import { FsManagerRPCService } from "./fs-manager.rpc-service";
8 |
9 | export class FsManagerBuilderRPCService extends RPCServiceOverIPC {
10 | static NAMESPACE = "local_sync_builder";
11 |
12 | private exposedWorkspacePaths = new Map();
13 |
14 | constructor() {
15 | super(FsManagerBuilderRPCService.NAMESPACE);
16 | this.init();
17 | }
18 |
19 | init() {
20 | this.exposeMethodOverIPC("createWorkspaceFolder", createWorkspaceFolder);
21 | this.exposeMethodOverIPC("getAllWorkspaces", getAllWorkspaces);
22 | this.exposeMethodOverIPC("removeWorkspace", removeWorkspace);
23 | this.exposeMethodOverIPC("build", this.build.bind(this));
24 | this.exposeMethodOverIPC("reload", this.reload.bind(this));
25 | }
26 |
27 | async build(rootPath: string) {
28 | if (this.exposedWorkspacePaths.has(rootPath)) {
29 | console.log("not building again");
30 | return;
31 | }
32 | const manager = new FsManagerRPCService(rootPath, this.exposedWorkspacePaths);
33 | await manager.init();
34 | this.exposedWorkspacePaths.set(rootPath, manager);
35 | }
36 |
37 | async reload(rootPath: string) {
38 | const manager = this.exposedWorkspacePaths.get(rootPath);
39 | if (!manager) {
40 | throw new Error(`FsManager not found for root path: ${rootPath}`);
41 | }
42 | manager.reload();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/actions/initGlobalState.js:
--------------------------------------------------------------------------------
1 | let isBackgroundProcessActive = false; // Default value
2 | /* Getter & Setter */
3 | Object.defineProperty(global, "isBackgroundProcessActive", {
4 | get() {
5 | return isBackgroundProcessActive;
6 | },
7 | set(value) {
8 | isBackgroundProcessActive = value;
9 | },
10 | });
11 |
12 | /**
13 | * NetworkRequestsLog is in state since it will help us persist old logs even when user navigates away from the Live Traffic table in the frontend
14 | * An alternative could be to log network traffic in the global state of the react app
15 | */
16 | let networkRequestsLog = []; // Default value = Empty logs
17 | /* Getter & Setter */
18 | Object.defineProperty(global, "networkRequestsLog", {
19 | get() {
20 | return networkRequestsLog;
21 | },
22 | set(value) {
23 | networkRequestsLog = value;
24 | },
25 | });
26 |
27 | let backgroundWindow = null; // Default value
28 | /* Getter & Setter */
29 | Object.defineProperty(global, "backgroundWindow", {
30 | get() {
31 | return backgroundWindow;
32 | },
33 | set(value) {
34 | backgroundWindow = value;
35 | },
36 | });
37 |
38 | let isQuitActionConfirmed = false; // Default value
39 | /* Getter & Setter */
40 | Object.defineProperty(global, "isQuitActionConfirmed", {
41 | get() {
42 | return isQuitActionConfirmed;
43 | },
44 | set(value) {
45 | isQuitActionConfirmed = value;
46 | },
47 | });
48 |
49 | let quitAndInstall = false; // Default value
50 | /* Getter & Setter */
51 | Object.defineProperty(global, "quitAndInstall", {
52 | get() {
53 | return quitAndInstall;
54 | },
55 | set(value) {
56 | quitAndInstall = value;
57 | },
58 | });
59 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/proxy/utils.js:
--------------------------------------------------------------------------------
1 | import UserPreferenceFetcher from "renderer/lib/proxy-interface/userPreferenceFetcher";
2 | import { isWindowsProxyRunning } from "./windows";
3 | const { exec, execSync } = require("child_process");
4 |
5 | const isProxyRunning = (proxy_output_str, port) => {
6 | return (
7 | proxy_output_str.includes("Enabled: Yes") &&
8 | proxy_output_str.includes(`Port: ${port}`)
9 | );
10 | };
11 |
12 | const isHttpProxyRunning = (port) => {
13 | try {
14 | const http_proxy_output = execSync(`networksetup -getwebproxy Wi-Fi`);
15 | return isProxyRunning(http_proxy_output, port);
16 | } catch (err) {
17 | console.error(err);
18 | console.log("Error while getwebproxy");
19 | }
20 | return false;
21 | };
22 |
23 | const isHttpsProxyRunning = (port) => {
24 | try {
25 | const https_proxy_output = execSync(
26 | `networksetup -getsecurewebproxy Wi-Fi`
27 | );
28 | return isProxyRunning(https_proxy_output, port);
29 | } catch (err) {
30 | console.error(err);
31 | console.log("Error while getsecurewebproxy");
32 | }
33 | return false;
34 | };
35 |
36 | export const getProxyStatus = (port) => {
37 | const userPreferences = new UserPreferenceFetcher();
38 | const DEFAULT_PROXY_PORT = userPreferences.getConfig().defaultPort;
39 | port = port || DEFAULT_PROXY_PORT;
40 |
41 | if (process.platform === "darwin") {
42 | return {
43 | http: isHttpProxyRunning(port),
44 | https: isHttpsProxyRunning(port),
45 | };
46 | } else if (process.platform === "win32") {
47 | const result = isWindowsProxyRunning(port);
48 | return {
49 | http: result,
50 | https: result,
51 | };
52 | }
53 |
54 | return {
55 | http: false,
56 | https: false,
57 | };
58 | };
59 |
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/httplib.py:
--------------------------------------------------------------------------------
1 | from httptoolkit_intercept import preload_real_module
2 |
3 | preload_real_module('httplib')
4 |
5 | import httplib, os, functools
6 |
7 | # Re-export all public fields
8 | from httplib import *
9 | # Load a few extra notable private fields, for max compatibility
10 | from httplib import __file__, __doc__
11 |
12 | _httpProxy = os.environ['HTTP_PROXY']
13 | [_proxyHost, _proxyPort] = _httpProxy.split('://')[1].split(':')
14 | _certPath = os.environ['SSL_CERT_FILE']
15 |
16 | # Redirect and then tunnel all plain HTTP connections:
17 | _http_connection_init = HTTPConnection.__init__
18 | @functools.wraps(_http_connection_init)
19 | def _new_http_connection_init(self, host, port=None, *k, **kw):
20 | _http_connection_init(self, _proxyHost, _proxyPort, *k, **kw)
21 | self.set_tunnel(host, port)
22 | HTTPConnection.__init__ = _new_http_connection_init
23 |
24 | def _build_default_context():
25 | import ssl
26 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
27 | context.options |= ssl.OP_NO_SSLv2
28 | context.options |= ssl.OP_NO_SSLv3
29 | return context
30 |
31 | # Redirect & tunnel HTTPS connections, and inject our CA certificate:
32 | _https_connection_init = HTTPSConnection.__init__
33 | @functools.wraps(_https_connection_init)
34 | def _new_https_connection_init(self, host, port=None, *k, **kw):
35 | context = None
36 | if 'context' in kw:
37 | context = kw.get('context')
38 | elif len(k) > 7:
39 | context = k[7]
40 |
41 | if context == None:
42 | context = kw['context'] = _build_default_context()
43 |
44 | context.load_verify_locations(_certPath)
45 |
46 | _https_connection_init(self, _proxyHost, _proxyPort, *k, **kw)
47 | self.set_tunnel(host, port)
48 | HTTPSConnection.__init__ = _new_https_connection_init
--------------------------------------------------------------------------------
/release/app/static/overrides/pythonpath/http/client.py:
--------------------------------------------------------------------------------
1 | from httptoolkit_intercept import preload_real_module
2 |
3 | preload_real_module('http', 'http.client')
4 |
5 | import http.client, os, functools
6 |
7 | # Re-export all public fields
8 | from http.client import *
9 | # Load a few extra notable private fields, for max compatibility
10 | from http.client import __file__, __doc__
11 |
12 | _httpProxy = os.environ['HTTP_PROXY']
13 | [_proxyHost, _proxyPort] = _httpProxy.split('://')[1].split(':')
14 | _certPath = os.environ['SSL_CERT_FILE']
15 |
16 | # Redirect and then tunnel all plain HTTP connections:
17 | _http_connection_init = HTTPConnection.__init__
18 | @functools.wraps(_http_connection_init)
19 | def _new_http_connection_init(self, host, port=None, *k, **kw):
20 | _http_connection_init(self, _proxyHost, int(_proxyPort), *k, **kw)
21 | self.set_tunnel(host, port)
22 | HTTPConnection.__init__ = _new_http_connection_init
23 |
24 | def _build_default_context():
25 | import ssl
26 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
27 | context.options |= ssl.OP_NO_SSLv2
28 | context.options |= ssl.OP_NO_SSLv3
29 | return context
30 |
31 | # Redirect & tunnel HTTPS connections, and inject our CA certificate:
32 | _https_connection_init = HTTPSConnection.__init__
33 | @functools.wraps(_https_connection_init)
34 | def _new_https_connection_init(self, host, port=None, *k, **kw):
35 | context = None
36 | if 'context' in kw:
37 | context = kw.get('context')
38 | elif len(k) > 7:
39 | context = k[7]
40 |
41 | if context == None:
42 | context = kw['context'] = _build_default_context()
43 |
44 | context.load_verify_locations(_certPath)
45 |
46 | _https_connection_init(self, _proxyHost, int(_proxyPort), *k, **kw)
47 | self.set_tunnel(host, port)
48 | HTTPSConnection.__init__ = _new_https_connection_init
--------------------------------------------------------------------------------
/src/renderer/actions/fileManagement/index.js:
--------------------------------------------------------------------------------
1 | const { promisify } = require("es6-promisify");
2 | import * as fs from "fs";
3 | import * as tmp from "tmp";
4 | import * as rimraf from "rimraf";
5 | import * as Sentry from "@sentry/browser";
6 |
7 | const rmfr = require("rmfr");
8 |
9 | export const statFile = promisify(fs.stat);
10 | export const readFile = promisify(fs.readFile);
11 | export const readDir = promisify(fs.readdir);
12 | export const deleteFile = promisify(fs.unlink);
13 | export const checkAccess = promisify(fs.access);
14 | export const mkDir = promisify(fs.mkdir);
15 | export const writeFile = promisify(fs.writeFile);
16 | export const renameFile = promisify(fs.rename);
17 | export const copyFile = promisify(fs.copyFile);
18 |
19 | export const canAccess = (path) =>
20 | checkAccess(path)
21 | .then(() => true)
22 | .catch((error) => {
23 | Sentry.captureException(error);
24 | return false;
25 | });
26 |
27 | export const deleteFolder = rmfr;
28 |
29 | export const ensureDirectoryExists = (path) =>
30 | checkAccess(path).catch((error) => {
31 | Sentry.captureException(error);
32 | return mkDir(path, { recursive: true });
33 | });
34 |
35 | export const moveFile = async (oldPath, newPath) => {
36 | try {
37 | await renameFile(oldPath, newPath);
38 | } catch (e) {
39 | Sentry.captureException(e);
40 | if (e.code === "EXDEV") {
41 | // Cross-device - can't rename files across partions etc.
42 | // In that case, we fallback to copy then delete:
43 | await copyFile(oldPath, newPath);
44 | await deleteFile(oldPath);
45 | }
46 | }
47 | };
48 |
49 | export const createTmp = (options = {}) =>
50 | new Promise((resolve, reject) => {
51 | tmp.file(options, (err, path, fd, cleanupCallback) => {
52 | if (err) return reject(err);
53 | resolve({ path, fd, cleanupCallback });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.renderer.dev.dll.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from "webpack";
6 | import path from "path";
7 | import { merge } from "webpack-merge";
8 | import baseConfig from "./webpack.config.base";
9 | import webpackPaths from "./webpack.paths";
10 | import { dependencies } from "../../package.json";
11 | import checkNodeEnv from "../scripts/check-node-env";
12 |
13 | checkNodeEnv("development");
14 |
15 | const dist = webpackPaths.dllPath;
16 |
17 | export default merge(baseConfig, {
18 | context: webpackPaths.rootPath,
19 |
20 | devtool: "eval",
21 |
22 | mode: "development",
23 |
24 | target: "electron-renderer",
25 |
26 | externals: ["fsevents", "crypto-browserify"],
27 |
28 | /**
29 | * Use `module` from `webpack.config.renderer.dev.js`
30 | */
31 | module: require("./webpack.config.renderer.dev").default.module,
32 |
33 | entry: {
34 | renderer: Object.keys(dependencies || {}),
35 | },
36 |
37 | output: {
38 | path: dist,
39 | filename: "[name].dev.dll.js",
40 | library: {
41 | name: "renderer",
42 | type: "var",
43 | },
44 | },
45 |
46 | plugins: [
47 | new webpack.DllPlugin({
48 | path: path.join(dist, "[name].json"),
49 | name: "[name]",
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: "development",
63 | }),
64 |
65 | new webpack.LoaderOptionsPlugin({
66 | debug: true,
67 | options: {
68 | context: webpackPaths.srcPath,
69 | output: {
70 | path: webpackPaths.dllPath,
71 | },
72 | },
73 | }),
74 | ],
75 | });
76 |
--------------------------------------------------------------------------------
/src/main/preload-apis/IPC.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require("electron");
2 |
3 | const IPC = {};
4 |
5 | /**
6 | * Send a message/payload as an event
7 | * @param {Object} params
8 | * @param {string} params.eventName Name of the event to register
9 | * @param {string} params.payload The message we want to send
10 | */
11 | IPC.sendEventPayload = (eventName, eventPayload) =>
12 | ipcRenderer.send(eventName, eventPayload);
13 |
14 | /**
15 | * Invoke an event in the main process. Returns response
16 | * @param {Object} params
17 | * @param {string} params.eventName Name of the event to register
18 | * @param {string} params.payload The message we want to send
19 | */
20 | IPC.invokeEventInMain = (eventName, eventPayload) =>
21 | ipcRenderer.invoke(eventName, eventPayload);
22 |
23 | /**
24 | * Invoke an event in the background process abstracted through main. Returns response
25 | * @param {Object} params
26 | * @param {Object} params.ipcRenderer Only ipcRender is supported
27 | * @param {string} params.eventName Name of the event to register
28 | * @param {string} params.payload The message we want to send
29 | */
30 | IPC.invokeEventInBG = (eventName, eventPayload) =>
31 | ipcRenderer.invoke(
32 | "forward-event-from-webapp-to-background-and-await-reply",
33 | {
34 | actualPayload: eventPayload,
35 | eventName,
36 | }
37 | );
38 |
39 | /**
40 | * Register an event using IPC
41 | * @param {Object} params
42 | * @param {string} params.eventName Name of the event to register
43 | * @param {Function} params.eventHandler The event handler function
44 | */
45 | IPC.registerEvent = (eventName, eventHandler) =>
46 | ipcRenderer.on(eventName, (event, payload) => eventHandler(payload));
47 |
48 | /**
49 | * Unregister all listeners for an event using IPC
50 | * @param {Object} params
51 | * @param {string} params.eventName Name of the event to register
52 | */
53 | IPC.unregisterEvent = (eventName) =>
54 | ipcRenderer.removeAllListeners([eventName]);
55 |
56 | module.exports = IPC;
57 |
--------------------------------------------------------------------------------
/.github/workflows/release_desktop_app.yml:
--------------------------------------------------------------------------------
1 | name: Release Desktop App
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | build_type:
7 | description: "Build Type"
8 | required: true
9 | type: choice
10 | options:
11 | - ubuntu-latest
12 | - windows-latest
13 | # - macos-latest
14 | default: windows-latest
15 |
16 | jobs:
17 | release:
18 | env:
19 | EP_GH_IGNORE_TIME: true
20 | # APPLE_ID: ${{ secrets.apple_id }}
21 | # APPLE_ID_PASS: ${{ secrets.apple_id_pass }}
22 |
23 | if: github.ref == 'refs/heads/master'|| github.ref == 'refs/heads/production'
24 | runs-on: ${{ github.event.inputs.build_type }}
25 |
26 | steps:
27 | - name: EOL autocrlf input
28 | if: ${{ github.event.inputs.build_type != 'windows-2019' }}
29 | run: git config --global core.autocrlf input
30 | - name: EOL autocrlf true
31 | if: ${{ github.event.inputs.build_type == 'windows-2019' }}
32 | run: git config --global core.autocrlf true
33 | - name: Check out Git repository
34 | uses: actions/checkout@v2
35 |
36 | - name: Install Node.js, NPM and Yarn
37 | uses: actions/setup-node@v2
38 | with:
39 | node-version: 20.9.0
40 |
41 | - name: Install desktop app dependencies
42 | run: bash ./install.sh
43 |
44 | - name: Build/release Electron app
45 | uses: samuelmeuli/action-electron-builder@v1.6.0
46 | with:
47 | # GitHub token, automatically provided to the action
48 | # (No need to define this secret in the repo settings)
49 | github_token: ${{ secrets.publish_token }}
50 | package_root: "."
51 |
52 | mac_certs: ${{ secrets.mac_certs }}
53 | mac_certs_password: ${{ secrets.mac_certs_password }}
54 |
55 | windows_certs: ${{ secrets.windows_certs }}
56 | windows_certs_password: ${{ secrets.windows_certs_password }}
57 |
58 | # If the commit is tagged with a version (e.g. "v1.0.0"),
59 | # release the app after building
60 | release: true
61 |
--------------------------------------------------------------------------------
/src/lib/storage/types/action-types.ts:
--------------------------------------------------------------------------------
1 | export enum SSL_PROXYING {
2 | ENABLE_ALL = "SSL_PROXYING:ENABLE_ALL",
3 | DISABLE_ALL = "SSL_PROXYING:DISABLE_ALL",
4 |
5 | UPSERT_INCLUSION_LIST_SOURCE = "SSL_PROXYING:UPSERT_INCLUSION_LIST_SOURCE",
6 | DELETE_INCLUSION_LIST_SOURCE = "SSL_PROXYING:DELETE_INCLUSION_LIST_SOURCE",
7 | UPDATE_INCLUSION_LIST = "SSL_PROXYING:UPDATE_INCLUSION_LIST",
8 | CLEAR_INCLUSION_LIST = "SSL_PROXYING:CLEAR_INCLUSION_LIST",
9 |
10 | UPSERT_EXCLUSION_LIST_SOURCE = "SSL_PROXYING:UPSERT_EXCLUSION_LIST_SOURCE",
11 | DELETE_EXCLUSION_LIST_SOURCE = "SSL_PROXYING:DELETE_EXCLUSION_LIST_SOURCE",
12 | UPDATE_EXCLUSION_LIST = "SSL_PROXYING:UPDATE_EXCLUSION_LIST",
13 | CLEAR_EXCLUSION_LIST = "SSL_PROXYING:CLEAR_EXCLUSION_LIST",
14 |
15 | GET_ALL = "SSL_PROXYING:GET_ALL",
16 | GET_INCLUSION_LIST = "SSL_PROXYING:GET_INCLUSION_LIST",
17 | GET_EXCLUSION_LIST = "SSL_PROXYING:GET_EXCLUSION_LIST",
18 | }
19 |
20 | export enum USER_PREFERENCE {
21 | UPDATE_DEFAULT_PORT = "USER_PREFERENCE:UPDATE_DEFAULT_PORT",
22 |
23 | GET_ALL = "USER_PREFERENCE:GET_ALL",
24 | GET_DEFAULT_PORT = "USER_PREFERENCE:GET_DEFAULT_PORT",
25 |
26 | GET_COMPLETE_LOGGING_CONFIG = "USER_PREFERENCE:LOCAL_LOG_FILE:GET_ALL",
27 |
28 | GET_IS_LOGGING_ENABLED = "USER_PREFERENCE:LOCAL_LOG_FILE:GET_IS_ENABLED",
29 | GET_LOCAL_LOG_STORE_PATH = "USER_PREFERENCE:LOCAL_LOG_FILE:STORE_PATH",
30 | GET_LOG_FILTER = "USER_PREFERENCE:LOCAL_LOG_FILE:GET_FILTER", // returns list of string matched as URL contains
31 |
32 | SET_STORE_PATH = "USER_PREFERENCE:LOCAL_LOG_FILE:SET_STORE_PATH",
33 | SET_FILTER = "USER_PREFERENCE:LOCAL_LOG_FILE:SET_FILTER", // sets list of string matched as URL contains
34 | SET_IS_LOGGING_ENABLED = "USER_PREFERENCE:LOCAL_LOG_FILE:SET_IS_ENABLED",
35 |
36 | }
37 |
38 | export enum ACCESSED_FILES {
39 | ADD = "ACCESSED_FILES:ADD",
40 | GET_CATEGORY = "ACCESSED_FILES:GET_CATEGORY",
41 | GET_ALL = "ACCESSED_FILES:GET_ALL",
42 | REMOVE = "ACCESSED_FILES:REMOVE",
43 | }
44 |
45 | const ACTION_TYPES = {
46 | SSL_PROXYING,
47 | USER_PREFERENCE,
48 | ACCESSED_FILES,
49 | };
50 |
51 | export default ACTION_TYPES;
52 |
--------------------------------------------------------------------------------
/src/renderer/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { app } = require("@electron/remote");
3 | // PACKAGE.JSON
4 | const packageJson = require("../../../package.json");
5 | // CONSTANTS
6 | const STATIC_FILES_DIR = require("./sub/staticFilesDirectory");
7 |
8 | // Static config can't be modified by the user
9 | const staticConfig = {
10 | APP_NAME: packageJson.productName,
11 | PROXY_HOST: "127.0.0.1",
12 | BROWSER_CONFIG_PATH: path.resolve(
13 | unescape(app.getPath("appData")),
14 | "Requestly",
15 | ".browser-config"
16 | ),
17 | CERTS_PATH: path.resolve(
18 | unescape(app.getPath("appData")),
19 | "Requestly",
20 | ".tmp"
21 | ),
22 | ROOT_CERT_PATH: path.resolve(
23 | unescape(app.getPath("appData")),
24 | "Requestly",
25 | ".tmp",
26 | "certs",
27 | "ca.pem"
28 | ),
29 | CERT_NAME: "RQProxyCA",
30 | CERT_VALIDITY: {
31 | // Number of days - before the current date - Keep minimum 1 to avoid 12am date change issues
32 | START_BEFORE: 1,
33 | // Number of days - after the current date - Keep minimum 1 to avoid 12am date change issues
34 | // CAUTION : Increasing this count might affect current app users
35 | END_AFTER: 365,
36 | },
37 | // Notably, for this file, this is the same when either bundled or unbundled.
38 | // That's not true for most other files! Everything should use this instead of __dirname:
39 | STATIC_FILES_DIR: STATIC_FILES_DIR,
40 |
41 | PROXY_TEST_PAGE_URL: "http://amiusing.requestly.io",
42 | };
43 |
44 | module.exports.staticConfig = staticConfig;
45 |
46 | // User preferences can be modified by the user via the react app
47 | const userPreferences = {
48 | DEFAULT_PROXY_PORT: {
49 | key: "proxy_port",
50 | defaultValue: 8281,
51 | },
52 | START_APP_ON_SYSTEM_STARTUP: {
53 | key: "start_app_on_system_startup",
54 | defaultValue: false,
55 | },
56 | VISITOR_ID: {
57 | key: "visitor_id",
58 | defaultValue: "",
59 | },
60 | ERROR_TRACKING_ENABLED: {
61 | key: "error_tracking_enabled",
62 | defaultValue: true,
63 | },
64 | };
65 |
66 | module.exports.userPreferences = userPreferences;
67 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/browsers/close-warning-tab.js:
--------------------------------------------------------------------------------
1 | // import { getLocal } from "mockttp";
2 |
3 | // // The first tab that opens in a new Chrome/Edge window warns about dangerous flags.
4 | // // Closing it and immediately opening a new one is a bit cheeky, but
5 | // // is completely gets rid that, more or less invisibly:
6 | // // eslint-disable-next-line import/prefer-default-export
7 | // export class HideWarningServer {
8 | // constructor(config) {
9 | // this.config = config;
10 | // this.server = getLocal();
11 | // // Resolved once the server has seen at least once
12 | // // request for the warning-hiding page.
13 | // this.completedPromise = new Promise((resolve) => {
14 | // this.server.on("request", (req) => {
15 | // if (req.url.includes("hide-warning")) {
16 | // resolve();
17 | // }
18 | // });
19 | // });
20 | // }
21 |
22 | // async start(targetUrl) {
23 | // await this.server.start();
24 | // await this.server.get("/hide-warning").thenReply(
25 | // 200,
26 | // `
27 | //
28 | // ${this.config.appName} Warning Fix
29 | //
30 | //
33 | //
38 | //
39 | // This page should disappear momentarily. If it doesn't, click
40 | // this link
41 | //
42 | //
43 | // `,
44 | // { "content-type": "text/html" }
45 | // );
46 | // }
47 |
48 | // get host() {
49 | // return this.server.url.replace("https://", "");
50 | // }
51 |
52 | // get hideWarningUrl() {
53 | // return this.server.url.replace(/\/?$/, "/hide-warning");
54 | // }
55 |
56 | // async stop() {
57 | // await this.server.stop();
58 | // }
59 | // }
60 |
--------------------------------------------------------------------------------
/.erb/scripts/check-native-dep.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import chalk from "chalk";
3 | import { execSync } from "child_process";
4 | import { dependencies } from "../../package.json";
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 | const nativeDeps = fs
9 | .readdirSync("node_modules")
10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
11 | if (nativeDeps.length === 0) {
12 | process.exit(0);
13 | }
14 | try {
15 | // Find the reason for why the dependency is installed. If it is installed
16 | // because of a devDependency then that is okay. Warn when it is installed
17 | // because of a dependency
18 | const { dependencies: dependenciesObject } = JSON.parse(
19 | execSync(`npm ls ${nativeDeps.join(" ")} --json`).toString()
20 | );
21 | const rootDependencies = Object.keys(dependenciesObject);
22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
23 | dependenciesKeys.includes(rootDependency)
24 | );
25 | if (filteredRootDependencies.length > 0) {
26 | const plural = filteredRootDependencies.length > 1;
27 | console.log(`
28 | ${chalk.whiteBright.bgYellow.bold(
29 | "Webpack does not work with native dependencies."
30 | )}
31 | ${chalk.bold(filteredRootDependencies.join(", "))} ${
32 | plural ? "are native dependencies" : "is a native dependency"
33 | } and should be installed inside of the "./release/app" folder.
34 | First, uninstall the packages from "./package.json":
35 | ${chalk.whiteBright.bgGreen.bold("npm uninstall your-package")}
36 | ${chalk.bold(
37 | 'Then, instead of installing the package to the root "./package.json":'
38 | )}
39 | ${chalk.whiteBright.bgRed.bold("npm install your-package")}
40 | ${chalk.bold('Install the package to "./release/app/package.json"')}
41 | ${chalk.whiteBright.bgGreen.bold(
42 | "cd ./release/app && npm install your-package"
43 | )}
44 | Read more about native dependencies at:
45 | ${chalk.bold(
46 | "https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure"
47 | )}
48 | `);
49 | process.exit(1);
50 | }
51 | } catch (e) {
52 | console.log("Native dependencies could not be checked");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.main.prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from "path";
6 | import webpack from "webpack";
7 | import { merge } from "webpack-merge";
8 | import TerserPlugin from "terser-webpack-plugin";
9 | import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
10 | import baseConfig from "./webpack.config.base";
11 | import webpackPaths from "./webpack.paths";
12 | import checkNodeEnv from "../scripts/check-node-env";
13 | import deleteSourceMaps from "../scripts/delete-source-maps";
14 |
15 | checkNodeEnv("production");
16 | deleteSourceMaps();
17 |
18 | const devtoolsConfig =
19 | process.env.DEBUG_PROD === "true"
20 | ? {
21 | devtool: "source-map",
22 | }
23 | : {};
24 |
25 | export default merge(baseConfig, {
26 | ...devtoolsConfig,
27 |
28 | mode: "production",
29 |
30 | target: "electron-main",
31 |
32 | entry: {
33 | main: path.join(webpackPaths.srcMainPath, "main.ts"),
34 | preload: path.join(webpackPaths.srcMainPath, "preload.js"),
35 | },
36 |
37 | output: {
38 | path: webpackPaths.distMainPath,
39 | filename: "[name].js",
40 | },
41 |
42 | optimization: {
43 | minimizer: [
44 | new TerserPlugin({
45 | parallel: true,
46 | }),
47 | ],
48 | },
49 |
50 | plugins: [
51 | new BundleAnalyzerPlugin({
52 | analyzerMode:
53 | process.env.OPEN_ANALYZER === "true" ? "server" : "disabled",
54 | openAnalyzer: process.env.OPEN_ANALYZER === "true",
55 | }),
56 |
57 | /**
58 | * Create global constants which can be configured at compile time.
59 | *
60 | * Useful for allowing different behaviour between development builds and
61 | * release builds
62 | *
63 | * NODE_ENV should be production so that modules do not perform certain
64 | * development checks
65 | */
66 | new webpack.EnvironmentPlugin({
67 | NODE_ENV: "production",
68 | DEBUG_PROD: false,
69 | START_MINIMIZED: false,
70 | IS_SETAPP_BUILD: false,
71 | }),
72 | ],
73 |
74 | /**
75 | * Disables webpack processing of __dirname and __filename.
76 | * If you run the bundle in node.js it falls back to these values of node.js.
77 | * https://github.com/webpack/webpack/issues/2010
78 | */
79 | node: {
80 | __dirname: false,
81 | __filename: false,
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/src/renderer/actions/startHelperServer.js:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from "electron";
2 | import { staticConfig } from "../config";
3 |
4 | const http = require("http");
5 |
6 | const pemPath = staticConfig.ROOT_CERT_PATH;
7 |
8 | function trackHelperServerHit() {
9 | ipcRenderer.invoke("helper-server-hit");
10 | }
11 |
12 | const getShellScript = (port) => `
13 | export http_proxy="http://127.0.0.1:${port}"
14 | export HTTP_PROXY="http://127.0.0.1:${port}"
15 | export https_proxy="http://127.0.0.1:${port}"
16 | export HTTPS_PROXY="http://127.0.0.1:${port}"
17 | export GLOBAL_AGENT_HTTP_PROXY="http://127.0.0.1:${port}"
18 | export CGI_HTTP_PROXY="http://127.0.0.1:${port}"
19 | export npm_config_proxy="http://127.0.0.1:${port}"
20 | export npm_config_https_proxy="http://127.0.0.1:${port}"
21 | export GOPROXY="http://127.0.0.1:${port}"
22 | export SSL_CERT_FILE="${pemPath}"
23 | export NODE_EXTRA_CA_CERTS="${pemPath}"
24 | export REQUESTS_CA_BUNDLE="${pemPath}"
25 | export PERL_LWP_SSL_CA_FILE="${pemPath}"
26 | export GIT_SSL_CAINFO="${pemPath}"
27 | export CARGO_HTTP_CAINFO="${pemPath}"
28 | export CURL_CA_BUNDLE="${pemPath}"
29 | if command -v winpty >/dev/null 2>&1; then
30 | # Work around for winpty's hijacking of certain commands
31 | alias php=php
32 | alias node=node
33 | fi
34 | if command -v winpty >/dev/null 2>&1; then
35 | # Work around for winpty's hijacking of certain commands
36 | alias php=php
37 | alias node=node
38 | fi
39 | echo 'Requestly interception enabled'
40 | `;
41 |
42 | let helperServer;
43 |
44 | export const stopHelperServer = async () => {
45 | if(helperServer) {
46 | helperServer?.close();
47 | helperServer = null;
48 | }
49 | }
50 |
51 |
52 | const startHelperServer = async (helperServerPort) => {
53 | if(helperServer) {
54 | stopHelperServer();
55 | }
56 | return new Promise((resolve) => {
57 | helperServer = http.createServer((req, res) => {
58 | console.log(window.proxy.httpPort);
59 | if (req.url === "/tpsetup") {
60 | const shellScript = getShellScript(window.proxy.httpPort);
61 | // const shellScript = `export http_proxy="http://127.0.0.1:${window.proxy.httpPort}"`
62 | trackHelperServerHit();
63 | res.writeHead(200, { "Content-Type": "text/x-shellscript" });
64 | res.write(shellScript);
65 | res.end();
66 | } else res.end("Invalid Request!");
67 | });
68 | helperServer.listen(helperServerPort, () => {
69 | resolve(true);
70 | });
71 | });
72 | };
73 |
74 | export default startHelperServer;
75 |
--------------------------------------------------------------------------------
/src/main/actions/getProxiedAxios.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance } from "axios";
2 | import { readFileSync } from "fs";
3 | import { HttpsProxyAgent, HttpsProxyAgentOptions } from "https-proxy-agent";
4 | import { ClientRequest, RequestOptions } from "agent-base";
5 | import {
6 | addCookiesToRequest,
7 | storeCookiesFromResponse,
8 | } from "./cookiesHelpers";
9 |
10 | class PatchedHttpsProxyAgent extends HttpsProxyAgent {
11 | ca: unknown;
12 |
13 | constructor(opts: HttpsProxyAgentOptions) {
14 | super(opts);
15 | this.ca = opts.ca;
16 | }
17 |
18 | async callback(req: ClientRequest, opts: RequestOptions) {
19 | return super.callback(req, Object.assign(opts, { ca: this.ca }));
20 | }
21 | }
22 |
23 | interface ProxyConfig {
24 | ip: string;
25 | port: number;
26 | rootCertPath: string;
27 | }
28 |
29 | let proxiedAxios: AxiosInstance;
30 | let proxiedAxiosWithSessionCookies: AxiosInstance;
31 | let proxyConfig: ProxyConfig;
32 |
33 | function createAxiosInstance(
34 | config: ProxyConfig,
35 | addStoredCookies: boolean = false
36 | ): AxiosInstance {
37 | const instance = axios.create({
38 | proxy: false,
39 | httpAgent: new HttpsProxyAgent(`http://${config.ip}:${config.port}`),
40 | httpsAgent: new PatchedHttpsProxyAgent({
41 | host: config.ip,
42 | port: config.port,
43 | ca: readFileSync(config.rootCertPath),
44 | }),
45 | });
46 |
47 | instance.interceptors.response.use(storeCookiesFromResponse);
48 | if (addStoredCookies) {
49 | instance.interceptors.request.use(addCookiesToRequest);
50 | }
51 | return instance;
52 | }
53 |
54 | export const createOrUpdateAxiosInstance = (
55 | newProxyConfig: ProxyConfig
56 | ): AxiosInstance => {
57 | proxyConfig = {
58 | ...(proxyConfig || {}),
59 | ...newProxyConfig,
60 | };
61 |
62 | try {
63 | proxiedAxios = createAxiosInstance(proxyConfig);
64 | proxiedAxiosWithSessionCookies = createAxiosInstance(proxyConfig, true);
65 | } catch (error) {
66 | /* Do nothing */
67 | console.error("Error creating or updating Axios instance:", error);
68 | }
69 |
70 | return proxiedAxios;
71 | };
72 |
73 | /*
74 | [Intentional] add cookies by default. In line with emulating browser behaviour.
75 | A better name could be excludeCredentials=false .
76 | did this because a flag called `withCredentials` has now been released for extension
77 | */
78 | const getProxiedAxios = (includeCredentials: boolean = true): AxiosInstance => {
79 | if (includeCredentials)
80 | return proxiedAxiosWithSessionCookies ?? proxiedAxios ?? axios;
81 | return proxiedAxios ?? axios;
82 | };
83 |
84 | export default getProxiedAxios;
85 |
--------------------------------------------------------------------------------
/src/renderer/lib/RPCServiceOverIPC.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from "electron";
2 |
3 | /**
4 | * Used to create a RPC like service in the Background process.
5 | * Has a corresponding Adapter class in the webapp repository.
6 | * --------------------------------
7 | * - Expects each RPC method to be exposed individually using `exposeMethodOverIPC`.
8 | * - Requires Arguments and Responses of those methods to be serializable. (limitation imposed by electron IPC)
9 | * - Currently only allows one generic fire-and-forget event channel for the service to send events to the webapp -> relayed over "send-from-background-to-webapp" channel.
10 | */
11 | export class RPCServiceOverIPC {
12 | private RPC_CHANNEL_PREFIX: string;
13 |
14 | private LIVE_EVENTS_CHANNEL: string;
15 |
16 | constructor(serviceName: string) {
17 | this.RPC_CHANNEL_PREFIX = `${serviceName}-`;
18 | this.LIVE_EVENTS_CHANNEL = `SERVICE-${serviceName}-LIVE-EVENTS`;
19 | }
20 |
21 | generateChannelNameForMethod(method: Function) {
22 | console.log("DBG-1: method name", method.name);
23 | return `${this.RPC_CHANNEL_PREFIX}${method.name}`;
24 | }
25 |
26 | protected exposeMethodOverIPC(
27 | exposedMethodName: string,
28 | method: (..._args: any[]) => Promise
29 | ) {
30 | const channelName = `${this.RPC_CHANNEL_PREFIX}${exposedMethodName}`;
31 | // console.log("DBG-1: exposing channel", channelName, Date.now());
32 | ipcRenderer.on(channelName, async (_event, args) => {
33 | // console.log(
34 | // "DBG-1: received event on channel",
35 | // channelName,
36 | // _event,
37 | // args,
38 | // Date.now()
39 | // );
40 | try {
41 | const result = await method(...args);
42 |
43 | // console.log(
44 | // "DBG-2: result in method",
45 | // result,
46 | // channelName,
47 | // _event,
48 | // args,
49 | // exposedMethodName,
50 | // Date.now()
51 | // );
52 | ipcRenderer.send(`reply-${channelName}`, {
53 | success: true,
54 | data: result,
55 | });
56 | } catch (error: any) {
57 | // console.log(
58 | // `DBG-2: reply-${channelName} error in method`,
59 | // error,
60 | // Date.now()
61 | // );
62 | ipcRenderer.send(`reply-${channelName}`, {
63 | success: false,
64 | data: error.message,
65 | });
66 | }
67 | });
68 | }
69 |
70 | sendServiceEvent(event: any) {
71 | return ipcRenderer.send("send-from-background-to-webapp", {
72 | channel: this.LIVE_EVENTS_CHANNEL,
73 | payload: event,
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/file-types/file-types.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file */
2 |
3 | import { FileType } from "./file-type.interface";
4 | import {
5 | ApiRecord,
6 | Auth,
7 | Description,
8 | EnvironmentRecord,
9 | GlobalConfig,
10 | Variables,
11 | } from "../schemas";
12 | import { FileTypeEnum } from "../types";
13 | import { parseJsonContent, parseRaw } from "../common-utils";
14 |
15 | export class ApiRecordFileType extends FileType {
16 | validator = ApiRecord;
17 |
18 | type = FileTypeEnum.API;
19 |
20 | parse(content: string) {
21 | return parseJsonContent(content, this.validator);
22 | }
23 | }
24 |
25 | export class EnvironmentRecordFileType extends FileType<
26 | typeof EnvironmentRecord
27 | > {
28 | validator = EnvironmentRecord;
29 |
30 | type = FileTypeEnum.ENVIRONMENT;
31 |
32 | parse(content: string) {
33 | return parseJsonContent(content, this.validator);
34 | }
35 | }
36 |
37 | export class CollectionVariablesRecordFileType extends FileType<
38 | typeof Variables
39 | > {
40 | validator = Variables;
41 |
42 | type = FileTypeEnum.COLLECTION_VARIABLES;
43 |
44 | parse(content: string) {
45 | return parseJsonContent(content, this.validator);
46 | }
47 | }
48 |
49 | export class ReadmeRecordFileType extends FileType {
50 | validator = Description;
51 |
52 | type = FileTypeEnum.DESCRIPTION;
53 |
54 | parse(content: string) {
55 | return parseRaw(content, this.validator);
56 | }
57 | }
58 |
59 | export class AuthRecordFileType extends FileType {
60 | validator = Auth;
61 |
62 | type = FileTypeEnum.AUTH;
63 |
64 | parse(content: string) {
65 | return parseJsonContent(content, this.validator);
66 | }
67 | }
68 |
69 | export class GlobalConfigRecordFileType extends FileType {
70 | validator = GlobalConfig;
71 |
72 | type = FileTypeEnum.GLOBAL_CONFIG;
73 |
74 | parse(content: string) {
75 | return parseJsonContent(content, this.validator);
76 | }
77 | }
78 |
79 | export function parseFileType(type: string) {
80 | switch (type) {
81 | case FileTypeEnum.API:
82 | return new ApiRecordFileType();
83 | case FileTypeEnum.ENVIRONMENT:
84 | return new EnvironmentRecordFileType();
85 | case FileTypeEnum.COLLECTION_VARIABLES:
86 | return new CollectionVariablesRecordFileType();
87 | case FileTypeEnum.DESCRIPTION:
88 | return new ReadmeRecordFileType();
89 | case FileTypeEnum.AUTH:
90 | return new AuthRecordFileType();
91 | case FileTypeEnum.GLOBAL_CONFIG:
92 | return new GlobalConfigRecordFileType();
93 | default:
94 | throw new Error(`${type} is an invalid file type`);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/lib/storage/index.ts:
--------------------------------------------------------------------------------
1 | import AccessedFilesProcessor from "./action-processors/accessed-files";
2 | import BaseActionProcessor from "./action-processors/base";
3 | import SSLProxyingActionProcessor from "./action-processors/ssl-proxying";
4 | import UserPreferenceActionProcessor from "./action-processors/user-preference";
5 | import { STORE_NAME } from "./constants";
6 | import { userPreferenceSchema } from "./schemas/userPreferenceSchema";
7 | import StoreWrapper from "./store-wrapper";
8 | import { StorageAction } from "./types/storage-action";
9 |
10 | class StorageService {
11 | actionProcessors: BaseActionProcessor[];
12 |
13 | sslProxyingStore!: StoreWrapper;
14 |
15 | userPreferenceStore!: StoreWrapper;
16 |
17 | accesssedFileStore!: StoreWrapper;
18 |
19 | sslProxyingActionProcessor!: BaseActionProcessor;
20 |
21 | userPreferenceActionProcessor!: BaseActionProcessor;
22 |
23 | accessFileActionProcessor!: BaseActionProcessor;
24 |
25 | constructor() {
26 | this.actionProcessors = [];
27 | this.init();
28 | }
29 |
30 | init = () => {
31 | this.initSSLProxying();
32 | this.initUserPreferences();
33 | this.initAccessFileStore();
34 | };
35 |
36 | initSSLProxying = () => {
37 | const storeName = STORE_NAME.SSL_PROXYING;
38 | this.sslProxyingStore = new StoreWrapper(storeName);
39 | this.sslProxyingActionProcessor = new SSLProxyingActionProcessor(
40 | this.sslProxyingStore
41 | );
42 | this.actionProcessors.push(this.sslProxyingActionProcessor);
43 | };
44 |
45 | initUserPreferences = () => {
46 | const storeName = STORE_NAME.USER_PREFERENCE;
47 | this.userPreferenceStore = new StoreWrapper(
48 | storeName,
49 | userPreferenceSchema
50 | );
51 | this.userPreferenceActionProcessor = new UserPreferenceActionProcessor(
52 | this.userPreferenceStore
53 | );
54 | this.actionProcessors.push(this.userPreferenceActionProcessor);
55 | };
56 |
57 | initAccessFileStore = () => {
58 | const storeName = STORE_NAME.ACCESSED_FILES;
59 | this.accesssedFileStore = new StoreWrapper(storeName);
60 | this.accessFileActionProcessor = new AccessedFilesProcessor(
61 | this.accesssedFileStore
62 | );
63 | this.actionProcessors.push(this.accessFileActionProcessor);
64 | };
65 |
66 | /**
67 | *
68 | * @param {*} action: {type: ACTION_TYPES, payload: any}
69 | */
70 | processAction = (action: StorageAction) => {
71 | let result: any;
72 | this.actionProcessors.forEach((processor) => {
73 | if (result) return;
74 | result = processor.process(action);
75 | });
76 |
77 | return result;
78 | };
79 | }
80 |
81 | const storageService = new StorageService();
82 |
83 | export default storageService;
84 |
--------------------------------------------------------------------------------
/src/main/actions/cookiesHelpers.ts:
--------------------------------------------------------------------------------
1 | import { Cookie, CookieJar } from "tough-cookie";
2 | import type { AxiosResponse, AxiosRequestConfig } from "axios";
3 | import Store from "electron-store";
4 |
5 | let cookieJar = new CookieJar(undefined, { looseMode: true });
6 |
7 | const offlineStore = new Store({
8 | name: "cookies",
9 | cwd: "storage",
10 | schema: {
11 | cookies: {
12 | type: "string",
13 | default: JSON.stringify({}),
14 | },
15 | },
16 | });
17 |
18 | export const loadCookies = () => {
19 | console.log("Loading cookies from offline store...");
20 | const rawCookieJarDump = offlineStore.get("cookies");
21 | let offlineJar: any;
22 | if (rawCookieJarDump) {
23 | // @ts-ignore
24 | offlineJar = JSON.parse(rawCookieJarDump);
25 | }
26 | if (!offlineJar || !offlineJar.cookies) {
27 | console.log(
28 | "No cookies found in offline store.",
29 | !!offlineJar,
30 | offlineJar?.cookies
31 | );
32 | return;
33 | }
34 |
35 | // @ts-ignore
36 | cookieJar = CookieJar.fromJSON(offlineJar);
37 | };
38 |
39 | export const saveCookies = () => {
40 | const cookiesToStore = JSON.stringify(cookieJar.toJSON());
41 | offlineStore.set("cookies", cookiesToStore);
42 | };
43 |
44 | loadCookies();
45 |
46 | export const storeCookiesFromResponse = (
47 | response: AxiosResponse
48 | ): AxiosResponse => {
49 | let cookies =
50 | response.headers["set-cookie"] || response.headers["Set-Cookie"];
51 | cookies = cookies ? (Array.isArray(cookies) ? cookies : [cookies]) : [];
52 | const finalURL: string =
53 | response.request?.res?.responseUrl || // to follow redirect
54 | response.config.url ||
55 | "";
56 | cookies.forEach((cookie: string) => {
57 | cookieJar.setCookieSync(cookie, finalURL, {ignoreError: true});
58 | });
59 | return response;
60 | };
61 |
62 | export const addCookiesToRequest = (request: AxiosRequestConfig) => {
63 | if (!request || !request.url) return request;
64 | const { url } = request;
65 | const storedCookies = cookieJar.getCookiesSync(url);
66 | const currentCookies =
67 | request.headers?.Cookie || request.headers?.cookie || "";
68 | const cookieString = storedCookies
69 | .map((cookie) => `${cookie.key}=${cookie.value}`)
70 | .join("; ");
71 | const headers = request.headers || {};
72 | const allCookies = [currentCookies, cookieString].filter(Boolean).join("; ");
73 | const finalCookie = Cookie.parse(allCookies, {
74 | // to allow key-less cookies (non-compliant to OG RFC 6265)
75 | // https://github.com/httpwg/http-extensions/issues/159
76 | loose: true,
77 | });
78 | if (finalCookie) {
79 | headers.Cookie = finalCookie?.toString();
80 | }
81 | request.headers = headers;
82 | return request;
83 | };
84 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fs/fs-unix.ts:
--------------------------------------------------------------------------------
1 | import { PathLike } from "node:fs";
2 | import { type FsService } from "./fs.service";
3 | import { FileHandle } from "node:fs/promises";
4 |
5 | function sanitizePath(rawPath: PathLike | FileHandle) {
6 | let path: string;
7 |
8 | if (rawPath instanceof Buffer || rawPath instanceof URL) {
9 | path = rawPath.toString();
10 | } else if (typeof rawPath === "string") {
11 | path = rawPath;
12 | } else {
13 | throw new Error("unsupported path type");
14 | }
15 |
16 | return path
17 | .replace(/\\/g, "\\\\") // Escape backslashes first
18 | .replace(/'/g, "\\'") // Escape single quotes
19 | .replace(/"/g, '\\"') // Escape double quotes
20 | .replace(/ /g, "\\ ") // Escape spaces
21 | .replace(/\$/g, "\\$") // Escape dollar sign
22 | .replace(/\*/g, "\\*") // Escape asterisk
23 | .replace(/\?/g, "\\?") // Escape question mark
24 | .replace(/&/g, "\\&") // Escape ampersand
25 | .replace(/\|/g, "\\|") // Escape pipe
26 | .replace(/;/g, "\\;") // Escape semicolon
27 | .replace(//g, "\\>") // Escape greater than
29 | .replace(/`/g, "\\`"); // Escape backtick
30 | }
31 |
32 | /*
33 | @TODO: This needs to consume string | NodeJS.ArrayBufferView | Iterable | AsyncIterable | internal.Stream
34 | and convert it into string that can be used in a shell command
35 | */
36 |
37 | export class FsUnix {
38 | static writeFile(...params: Parameters): string {
39 | const path = sanitizePath(params[0]);
40 | return `echo '${params[1]}' > ${path}`;
41 | }
42 |
43 | static unlink(...params: Parameters): string {
44 | const path = sanitizePath(params[0]);
45 | return `rm -f ${path}`;
46 | }
47 |
48 | static mkdir(...params: Parameters): string {
49 | const path = sanitizePath(params[0]);
50 | return `mkdir -p ${path}`;
51 | }
52 |
53 | static rmdir(...params: Parameters): string {
54 | const path = sanitizePath(params[0]);
55 | return `rm -rf ${path}`;
56 | }
57 |
58 | static rename(...params: Parameters): string {
59 | const source = sanitizePath(params[0]);
60 | const destination = sanitizePath(params[1]);
61 | return `mv ${source} ${destination}`;
62 | }
63 |
64 | static cp(...params: Parameters): string {
65 | const path = sanitizePath(params[0]);
66 | return `cp -r ${path} ${params[1]}`;
67 | }
68 |
69 | static readFile(...params: Parameters): string {
70 | const path = sanitizePath(params[0]);
71 | return `cat ${path}`;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/os/ca/utils.js:
--------------------------------------------------------------------------------
1 | import { deleteOsxCert } from "./osx";
2 | import { deleteWindowsCert } from "./windows";
3 |
4 | const { execSync } = require("child_process");
5 |
6 | export const isCertificateInstalled = () => {
7 | let status = false;
8 | let command;
9 | switch (process.platform) {
10 | case "darwin":
11 | command = `security find-certificate -c RQProxyCA $HOME/Library/Keychains/login.keychain`;
12 | break;
13 | case "win32":
14 | command = `certutil -user -verifystore Root RQProxyCA`;
15 | break;
16 | default:
17 | console.log(`${process.platform} is not supported for systemwide proxy`);
18 | return false;
19 | }
20 |
21 | try {
22 | status = !!execSync(command);
23 | console.log("Found CA already installed");
24 | } catch (err) {
25 | console.error(err);
26 | console.log("CA not found");
27 | }
28 | // console.log(status);
29 | return status;
30 | };
31 |
32 | const is_rq_cert_trusted = (trust_settings_str) => {
33 | let re =
34 | /(Cert \d+: RQProxyCA[\s\S]*?(?=Cert))|(Cert \d+: RQProxyCA[\s\S]*)/gm;
35 | const rq_cert_settings = re.exec(trust_settings_str);
36 |
37 | if (
38 | rq_cert_settings &&
39 | !rq_cert_settings[0].includes("kSecTrustSettingsResultDeny")
40 | ) {
41 | return true;
42 | }
43 |
44 | return false;
45 | };
46 |
47 | export const isCertificateTrusted = () => {
48 | switch (process.platform) {
49 | case "darwin":
50 | try {
51 | const trust_settings_output = execSync(`security dump-trust-settings`);
52 | return is_rq_cert_trusted(trust_settings_output);
53 | } catch (err) {
54 | console.error(err);
55 | console.log("Trust Settings not found");
56 | return false;
57 | }
58 | case "win32": // in windows, if cert is installed, it is trusted
59 | return isCertificateInstalled();
60 | default:
61 | console.log(`${process.platform} is not supported for systemwide proxy`);
62 | return false;
63 | }
64 | };
65 |
66 | export const handleCARegeneration = (pathToNewCA) => {
67 | console.log("new CA generated at", pathToNewCA);
68 | switch (process.platform) {
69 | case "darwin": {
70 | deleteOsxCert("RQProxyCA");
71 | break;
72 | }
73 | case "win32": {
74 | deleteWindowsCert("RQProxyCA");
75 | break;
76 | }
77 | default: {
78 | console.log("No extra steps after CA regeneration");
79 | }
80 | }
81 | };
82 |
83 | export const getCertStatus = () => {
84 | if (process.platform === "darwin" || process.platform === "win32") {
85 | return {
86 | installed: isCertificateInstalled(),
87 | trusted: isCertificateTrusted(),
88 | };
89 | }
90 |
91 | return {
92 | installed: false,
93 | trusted: false,
94 | };
95 | };
96 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fs/sudoCommandExecutor.ts:
--------------------------------------------------------------------------------
1 | import { FsUnix } from "./fs-unix";
2 | import { type FsService } from "./fs.service";
3 | import { FsCommandProvider } from "../types";
4 |
5 | const sudo = require("@vscode/sudo-prompt");
6 |
7 | const options = {
8 | name: "Requestly",
9 | icns: "/Applications/Requestly.app/Contents/Resources/icon.icns",
10 | };
11 |
12 | function getProvider(): FsCommandProvider {
13 | if (process.platform === "darwin" || process.platform === "linux") {
14 | return FsUnix;
15 | }
16 | throw new Error(`Unsupported platform ${process.platform}`);
17 | }
18 |
19 | export class SudoCommandExecutor {
20 | private static execCommand(command: string) {
21 | return new Promise((resolve, reject) => {
22 | sudo.exec(command, options, (error: any, stdout: string) => {
23 | if (error) {
24 | console.error("DBG error rejecting:", error);
25 | return reject(error);
26 | }
27 | return resolve(stdout);
28 | });
29 | });
30 | }
31 |
32 | static async writeFile(
33 | ...params: Parameters
34 | ): ReturnType {
35 | const command = getProvider().writeFile(...params);
36 | await SudoCommandExecutor.execCommand(command);
37 | }
38 |
39 | static async unlink(
40 | ...params: Parameters
41 | ): ReturnType {
42 | const command = getProvider().unlink(...params);
43 | await SudoCommandExecutor.execCommand(command);
44 | }
45 |
46 | static async mkdir(
47 | ...params: Parameters
48 | ): ReturnType {
49 | const command = getProvider().mkdir(...params);
50 | return SudoCommandExecutor.execCommand(command);
51 | }
52 |
53 | static async rmdir(
54 | ...params: Parameters
55 | ): ReturnType {
56 | const command = getProvider().rmdir(...params);
57 | await SudoCommandExecutor.execCommand(command);
58 | }
59 |
60 | static async rename(
61 | ...params: Parameters
62 | ): ReturnType {
63 | const command = getProvider().rename(...params);
64 | await SudoCommandExecutor.execCommand(command);
65 | }
66 |
67 | static async cp(
68 | ...params: Parameters
69 | ): ReturnType {
70 | const command = getProvider().cp(...params);
71 | await SudoCommandExecutor.execCommand(command);
72 | }
73 |
74 | static async readFile(
75 | ...params: Parameters
76 | ): ReturnType {
77 | const command = getProvider().readFile(...params);
78 | return SudoCommandExecutor.execCommand(command);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/actions/startBackgroundProcess.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { enable as enableWebContents } from "@electron/remote/main";
3 | /** Babel */
4 | require("core-js/stable");
5 | require("regenerator-runtime/runtime");
6 | const path = require("path");
7 | // CORE
8 | // Utils
9 | const { BrowserWindow } = require("electron");
10 | const logger = require("../../utils/logger");
11 | // State Management
12 | const { getState, setState } = require("./stateManagement");
13 | const { setupIPCForwardingToBackground } = require("./setupIPCForwarding");
14 |
15 | const resolveBackgroundPath = (htmlFileName) => {
16 | if (process.env.NODE_ENV === "development") {
17 | const port = process.env.PORT || 1212;
18 | const url = new URL(`http://localhost:${port}`);
19 | url.pathname = htmlFileName;
20 | return url.href;
21 | }
22 | return `file://${path.resolve(__dirname, "../renderer/", htmlFileName)}`;
23 | };
24 |
25 | const startBackgroundProcess = async () => {
26 | // eslint-disable-next-line no-async-promise-executor
27 | return new Promise(async (resolve) => {
28 | let backgroundWindow = await getState("backgroundWindow");
29 | if (!backgroundWindow) {
30 | backgroundWindow = await setState(null, {
31 | stateName: "backgroundWindow",
32 | // Background Process Window
33 | newValue: new BrowserWindow({
34 | width: 800,
35 | height: 600,
36 | show:
37 | process.env.NODE_ENV === "development" ||
38 | process.env.DEBUG_PROD === "true",
39 | webPreferences: {
40 | nodeIntegration: true,
41 | contextIsolation: false,
42 | enableRemoteModule: true,
43 | },
44 | }),
45 | });
46 | enableWebContents(backgroundWindow.webContents);
47 | } else {
48 | logger.log(
49 | "startBackgroundProcess: A background windows already exists. Cancelling."
50 | );
51 |
52 | resolve(true);
53 | return;
54 | }
55 |
56 | global.backgroundWindow = backgroundWindow;
57 |
58 | // Load background code
59 | backgroundWindow.loadURL(resolveBackgroundPath("index.html"));
60 |
61 | // Open the DevTools in dev mode
62 | if (
63 | process.env.NODE_ENV === "development" ||
64 | process.env.DEBUG_PROD === "true"
65 | )
66 | {
67 | backgroundWindow.webContents.once('dom-ready', () => {
68 | backgroundWindow.webContents.openDevTools();
69 | })
70 | }
71 |
72 | // Setup IPC forwarding
73 | setupIPCForwardingToBackground(backgroundWindow);
74 |
75 | // Set state
76 | global.isBackgroundProcessActive = true;
77 |
78 | backgroundWindow.webContents.on("did-finish-load", () => {
79 | resolve(true);
80 | });
81 |
82 | });
83 | };
84 |
85 | export default startBackgroundProcess;
86 |
--------------------------------------------------------------------------------
/src/packages/rq_proxy/ssl-proxying/ssl-proxying-manager.ts:
--------------------------------------------------------------------------------
1 | // import { ISource, SSLProxyingJsonObj } from "lib/storage/types/ssl-proxying";
2 | // import BaseConfigFetcher from "renderer/lib/proxy-interface/base";
3 | // // TODO: @sahil fix this by adding type.d.ts file
4 | // //@ts-ignore
5 | // import { RULE_PROCESSOR } from "@requestly/requestly-core";
6 |
7 | // class SSLProxyingManager {
8 | // configFetcher: BaseConfigFetcher;
9 |
10 | // constructor(configFetcher: BaseConfigFetcher) {
11 | // this.configFetcher = configFetcher;
12 | // }
13 |
14 | // isSslProxyingActive = (urlOrigin: string): boolean => {
15 | // const config: SSLProxyingJsonObj = this.configFetcher.getConfig();
16 |
17 | // if (config.enabledAll === false) {
18 | // const inclusionListSuccess: boolean = this.checkStatusWithInclusionList(
19 | // config,
20 | // urlOrigin
21 | // );
22 | // if (inclusionListSuccess) {
23 | // console.log(`${urlOrigin} inclusion List`);
24 | // return true;
25 | // }
26 |
27 | // return false;
28 | // } else {
29 | // const exclusionListSuccess: boolean = this.checkStatusWithExclusionList(
30 | // config,
31 | // urlOrigin
32 | // );
33 | // if (exclusionListSuccess) {
34 | // console.log(`${urlOrigin} exclusion List`);
35 | // return false;
36 | // }
37 |
38 | // return true;
39 | // }
40 | // };
41 |
42 | // checkStatusWithInclusionList = (
43 | // config: SSLProxyingJsonObj,
44 | // urlOrigin: string
45 | // ): boolean => {
46 | // const inclusionListSources: ISource[] = Object.values(
47 | // config.inclusionList || {}
48 | // );
49 | // return this.checkStatusWithList(inclusionListSources, urlOrigin);
50 | // };
51 |
52 | // checkStatusWithExclusionList = (
53 | // config: SSLProxyingJsonObj,
54 | // urlOrigin: string
55 | // ): boolean => {
56 | // const exclusionListSources: ISource[] = Object.values(
57 | // config.exclusionList || {}
58 | // );
59 | // return this.checkStatusWithList(exclusionListSources, urlOrigin);
60 | // };
61 |
62 | // checkStatusWithList = (
63 | // sourceObjs: ISource[] = [],
64 | // urlOrigin: string = ""
65 | // ): boolean => {
66 | // return sourceObjs.some((sourceObj) =>
67 | // this.checkStatusForSource(sourceObj, urlOrigin)
68 | // );
69 | // };
70 |
71 | // checkStatusForSource = (
72 | // sourceObject: ISource,
73 | // urlOrigin: string
74 | // ): boolean => {
75 | // const result = RULE_PROCESSOR.RuleMatcher.matchUrlWithRuleSource(
76 | // sourceObject,
77 | // urlOrigin
78 | // );
79 | // if (result === "") {
80 | // return true;
81 | // }
82 | // return false;
83 | // };
84 | // }
85 |
86 | // export default SSLProxyingManager;
87 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/appManager.js:
--------------------------------------------------------------------------------
1 | import * as _ from "lodash";
2 | // ACTIONS
3 | import {
4 | FreshChrome,
5 | ExistingChrome,
6 | FreshChromeBeta,
7 | FreshChromeCanary,
8 | FreshChromeDev,
9 | FreshChromium,
10 | FreshChromiumDev,
11 | FreshEdge,
12 | FreshEdgeBeta,
13 | FreshEdgeDev,
14 | FreshEdgeCanary,
15 | FreshBrave,
16 | FreshOpera,
17 | ExistingChromeBeta,
18 | ExistingChromeDev,
19 | ExistingEdge,
20 | ExistingArc,
21 | FreshOperaGX,
22 | FreshOperaCrypto,
23 | } from "./browsers/chromium-based-browsers";
24 | import { addShutdownHandler } from "../shutdown";
25 | import { Electron } from "./electron";
26 | import { FreshFirefox } from "./browsers/fresh-firefox";
27 | import { FreshSafari } from "./browsers/safari";
28 | import { SystemWideProxy } from "./os/system-wide";
29 | import { ipcRenderer } from "electron";
30 | import AndroidAdbDevice from "./mobile/android";
31 | import IosSimulatorDevice from "./mobile/iosSimulator";
32 |
33 | export const shutdownApps = (apps) => {
34 | return Promise.all(apps.map((i) => i.deactivateAll()));
35 | };
36 |
37 | export const buildApps = (config) => {
38 | const apps = [
39 | new FreshChrome(config),
40 | new ExistingChrome(config),
41 | new FreshChromeBeta(config),
42 | new ExistingChromeBeta(config),
43 | new FreshChromeDev(config),
44 | new ExistingChromeDev(config),
45 | new FreshChromeCanary(config),
46 | new FreshChromium(config),
47 | new FreshChromiumDev(config),
48 | new FreshEdge(config),
49 | new ExistingEdge(config),
50 | new FreshEdgeBeta(config),
51 | new FreshEdgeDev(config),
52 | new FreshEdgeCanary(config),
53 | new FreshOpera(config),
54 | new FreshOperaGX(config),
55 | new FreshOperaCrypto(config),
56 | new ExistingArc(config),
57 | new FreshBrave(config),
58 | new FreshFirefox(config),
59 | new Electron(config),
60 | new FreshSafari(config),
61 | new SystemWideProxy(config),
62 | new AndroidAdbDevice(config),
63 | new IosSimulatorDevice(config),
64 | ];
65 |
66 | // When the server exits, try to shut down the interceptors too
67 | addShutdownHandler(() => shutdownApps(apps));
68 |
69 | const appIndex = _.keyBy(apps, (app) => app.id);
70 |
71 | if (Object.keys(appIndex).length !== apps.length) {
72 | throw new Error("Duplicate app id");
73 | }
74 |
75 | /*
76 | * Extra IPC for now
77 | *
78 | * TODO in proxy : lib/proxy/lib/proxy.js -> Proxy.prototype.close
79 | * a cleaner way would be to directly add
80 | * shutdown/close handler inside the proxy
81 | * so no IPC call, every proxy restart would trigger
82 | * all added `closeHandlers`
83 | */
84 | ipcRenderer.on("deactivate-traffic-sources", async () => {
85 | await shutdownApps(apps);
86 | ipcRenderer.send("reply-deactivate-traffic-sources");
87 | });
88 |
89 | return appIndex;
90 | };
91 |
--------------------------------------------------------------------------------
/src/lib/storage/action-processors/accessed-files.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { ACCESSED_FILES } from "../types/action-types";
3 | import { StorageAction } from "../types/storage-action";
4 | import BaseActionProcessor from "./base";
5 |
6 | type AccessedFileCategoryTag = "web-session" | "har" | "unknown";
7 |
8 | export interface AccessedFile {
9 | filePath: string;
10 | category: AccessedFileCategoryTag;
11 | name: string;
12 | lastAccessedTs: number;
13 | id: string;
14 | }
15 |
16 | // not to be done in this class process
17 | export default class AccessedFilesProcessor extends BaseActionProcessor {
18 | process = ({ type, payload }: StorageAction) => {
19 | let category;
20 | let filePath: string;
21 | let name: string;
22 | let lastAccessedTs: number;
23 | if (type === ACCESSED_FILES.GET_CATEGORY) {
24 | console.log("get category", payload);
25 | // @ts-ignore // todo; remove this ignore
26 | category = payload?.data?.category as unknown as AccessedFileCategoryTag;
27 | const accessedFilesFromeStore = this.store.get(category) as Record<
28 | AccessedFile["filePath"],
29 | AccessedFile
30 | >;
31 |
32 | console.log("accessedFilesFromeStore", accessedFilesFromeStore )
33 | return Object.values(accessedFilesFromeStore).sort(
34 | (a, b) => b.lastAccessedTs - a.lastAccessedTs
35 | );
36 | }
37 |
38 | if (type === ACCESSED_FILES.ADD) {
39 | const file = payload?.data as AccessedFile;
40 | category = file.category;
41 | filePath = file.filePath;
42 | name = file.name;
43 | lastAccessedTs = file.lastAccessedTs;
44 |
45 | const accessedFilesRecords = (this.store.get(category) as any) || {};
46 | accessedFilesRecords[filePath] = {
47 | filePath,
48 | name,
49 | lastAccessedTs,
50 | category,
51 | id: filePath,
52 | };
53 | this.store.set({ [category]: accessedFilesRecords });
54 | } else if (type === ACCESSED_FILES.REMOVE) {
55 | const getFileCategory = (fileExtension: string) => {
56 | switch(fileExtension) {
57 | case ".har":
58 | return "har";
59 | case ".rqly":
60 | // in future we can also store rules here...
61 | return "web-session";
62 | default:
63 | return "unknown";
64 | }
65 | }
66 | // @ts-ignore
67 | filePath = payload?.data as string;
68 | const extension = path.extname(filePath);
69 | category = getFileCategory(extension) as AccessedFileCategoryTag;
70 | const accessedFilesRecords = this.store.get(category);
71 | delete accessedFilesRecords[filePath];
72 | this.store.set({ [category]: accessedFilesRecords });
73 | } else {
74 | console.log("unexpected accessed file action", type, payload);
75 | }
76 | return null;
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/release/app/static/overrides/js/wrap-require.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Intercept calls to require() certain modules, to monkey-patch them.
3 | *
4 | * This modules intercepts all require calls. For all modules previously
5 | * registered via wrapModule, it runs the registered wrapper on the loaded
6 | * module before it is returned to the original require() call.
7 | */
8 |
9 | // Grab the built-in module loader that we're going to intercept
10 | const mod = require("module");
11 | const realLoad = mod._load;
12 |
13 | const wrappers = {};
14 |
15 | // Either false, or a list (initially empty) of modules whose wrapping is being
16 | // delayed. This is important for modules who require other modules that need
17 | // wrapping, to avoid issues with circular requires.
18 | let wrappingBlocked = false;
19 |
20 | function fixModule(requestedName, filename, loadedModule) {
21 | const wrapper = wrappers[requestedName];
22 |
23 | if (wrapper) {
24 | wrappingBlocked = wrapper.shouldBlockWrapping ? [] : false;
25 |
26 | // wrap can either return a replacement, or mutate the module itself.
27 | const fixedModule = wrapper.wrap(loadedModule) || loadedModule;
28 |
29 | if (
30 | fixedModule !== loadedModule &&
31 | mod._cache[filename] &&
32 | mod._cache[filename].exports
33 | ) {
34 | mod._cache[filename].exports = fixedModule;
35 | }
36 |
37 | if (wrappingBlocked) {
38 | wrappingBlocked.forEach(function (modDetails) {
39 | fixModule(
40 | modDetails.requestedName,
41 | modDetails.filename,
42 | modDetails.loadedModule
43 | );
44 | });
45 | wrappingBlocked = false;
46 | }
47 |
48 | return fixedModule;
49 | } else {
50 | return loadedModule;
51 | }
52 | }
53 |
54 | // Our hook into require():
55 | mod._load = function (requestedName, parent, isMain) {
56 | const filename = mod._resolveFilename(requestedName, parent, isMain);
57 | let loadedModule = realLoad.apply(this, arguments);
58 |
59 | // Should always be set, but check just in case. This also allows
60 | // users to disable interception explicitly, if need be.
61 | if (!process.env.REQUESTLY_ACTIVE) return loadedModule;
62 |
63 | if (wrappingBlocked !== false) {
64 | wrappingBlocked.push({
65 | requestedName: requestedName,
66 | filename: filename,
67 | loadedModule: loadedModule,
68 | });
69 | } else {
70 | loadedModule = fixModule(requestedName, filename, loadedModule);
71 | }
72 |
73 | return loadedModule;
74 | };
75 |
76 | // Register a wrapper for a given name. If shouldBlockWrapping is set, all wrapping
77 | // of modules require'd during the modules wrapper function will be delayed until
78 | // after it completes.
79 | module.exports = function wrapModule(
80 | requestedName,
81 | wrapperFunction,
82 | shouldBlockWrapping
83 | ) {
84 | wrappers[requestedName] = {
85 | wrap: wrapperFunction,
86 | shouldBlockWrapping: shouldBlockWrapping || false,
87 | };
88 | };
89 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/mobile/adb-commands.ts:
--------------------------------------------------------------------------------
1 | import * as adb from "@devicefarmer/adbkit";
2 | import { getCertificateFingerprint, parseCert } from "renderer/utils/cert";
3 | import stream from "stream";
4 |
5 | export async function rootDevice(
6 | adbClient: adb.Client,
7 | deviceId: string
8 | ): Promise {
9 | try {
10 | const output = await adbClient.getDevice(deviceId).root();
11 | console.log("Root Device", { output });
12 | return output;
13 | } catch (error: any) {
14 | if (error.message === "adbd is already running as root") {
15 | console.log("Device is already rooted");
16 | return true;
17 | }
18 |
19 | console.error(error);
20 | throw new Error(error?.message);
21 | }
22 | }
23 |
24 | export async function pushFile(
25 | adbClient: adb.Client,
26 | deviceId: string,
27 | contents: string | stream.Readable,
28 | path: string,
29 | mode?: number
30 | ) {
31 | const transfer = await adbClient
32 | .getDevice(deviceId)
33 | .push(contents, path, mode);
34 |
35 | return new Promise((resolve, reject) => {
36 | transfer.on("end", resolve);
37 | transfer.on("error", reject);
38 | });
39 | }
40 |
41 | export async function hasCertInstalled(
42 | adbClient: adb.Client,
43 | deviceId: string,
44 | certHash: string,
45 | certFingerprint: string
46 | ) {
47 | try {
48 | const certPath = `/data/misc/user/0/cacerts-added/${certHash}.0`;
49 | const certStream = await adbClient.getDevice(deviceId).pull(certPath);
50 |
51 | // Wait until it's clear that the read is successful
52 | const data = await new Promise((resolve, reject) => {
53 | // eslint-disable-next-line no-shadow
54 | const data: Uint8Array[] = [];
55 | certStream.on("data", (d: Uint8Array) => data.push(d));
56 | certStream.on("end", () => resolve(Buffer.concat(data)));
57 |
58 | certStream.on("error", reject);
59 | });
60 |
61 | console.log("Read data", { data: data.toString("utf8") });
62 | // The device already has an cert. But is it the right one?
63 | const existingCert = parseCert(data.toString("utf8"));
64 | const existingFingerprint = getCertificateFingerprint(existingCert);
65 | console.log({
66 | certHash,
67 | data: data.toString("utf8"),
68 | certFingerprint,
69 | existingFingerprint,
70 | });
71 | return certFingerprint === existingFingerprint;
72 | } catch (e) {
73 | // Couldn't read the cert, or some other error - either way, we probably
74 | // don't have a working system cert installed.
75 | console.log("Error reading cert", e);
76 | return false;
77 | }
78 | }
79 |
80 | export const checkProxy = async (adbClient: adb.Client, deviceId: string) => {
81 | return adbClient
82 | .getDevice(deviceId)
83 | .shell("settings get global http_proxy")
84 | .then(adb.Adb.util.readAll)
85 | .then((output: any) => {
86 | return output.toString().trim();
87 | })
88 | .catch((err: any) => {
89 | console.log("checkProxy", err);
90 | });
91 | };
92 |
--------------------------------------------------------------------------------
/src/lib/storage/action-processors/user-preference.ts:
--------------------------------------------------------------------------------
1 | import {USER_PREFERENCE} from "../types/action-types";
2 | import { StorageAction } from "../types/storage-action";
3 | import BaseActionProcessor from "./base";
4 | import StoreWrapper from "../store-wrapper";
5 |
6 | class UserPreferenceActionProcessor extends BaseActionProcessor {
7 | constructor(store: StoreWrapper) {
8 | super(store);
9 | }
10 | private updateKeyInLogConfig = (key: any, value: any) => {
11 | const loggingConfig = this.store.get("localFileLogConfig") ?? {}
12 | this.store.set({
13 | localFileLogConfig: {
14 | ...loggingConfig,
15 | [key]: value
16 | }
17 | })
18 | }
19 | private getKeyFromLogingConfig = (key: any) => {
20 | const loggingConfig = this.store.get("localFileLogConfig")
21 | if(loggingConfig) {
22 | return loggingConfig[key]
23 | }
24 | return undefined;
25 | }
26 | process = ({ type, payload }: StorageAction) => {
27 | switch(type) {
28 | case USER_PREFERENCE.GET_ALL:
29 | return this.store.getAll()
30 | case USER_PREFERENCE.GET_DEFAULT_PORT:
31 | return this.store.get("defaultPort")
32 | case USER_PREFERENCE.UPDATE_DEFAULT_PORT:
33 | this.store.set({"defaultPort": payload?.data})
34 | break;
35 | case USER_PREFERENCE.GET_COMPLETE_LOGGING_CONFIG:
36 | return this.store.get("localFileLogConfig")
37 |
38 | case USER_PREFERENCE.GET_IS_LOGGING_ENABLED: {
39 | return !!this.getKeyFromLogingConfig("isEnabled")
40 | }
41 | case USER_PREFERENCE.GET_LOCAL_LOG_STORE_PATH: {
42 | return this.getKeyFromLogingConfig("storePath") ?? ""
43 | }
44 | case USER_PREFERENCE.GET_LOG_FILTER: {
45 | return this.getKeyFromLogingConfig("filter") ?? []
46 | }
47 |
48 | case USER_PREFERENCE.SET_IS_LOGGING_ENABLED: {
49 | // @ts-ignore
50 | let isEnabledPayload = payload?.data?.isLocalLoggingEnabled
51 | if (typeof isEnabledPayload === "boolean") {
52 | this.updateKeyInLogConfig("isEnabled", isEnabledPayload)
53 | }
54 |
55 | if(typeof isEnabledPayload === "string") { // serialization in IPC
56 | if(
57 | isEnabledPayload === "false" ||
58 | isEnabledPayload === "0"
59 | ) {
60 | this.updateKeyInLogConfig("isEnabled", false)
61 | } else {
62 | this.updateKeyInLogConfig("isEnabled", true)
63 | }
64 | }
65 |
66 | break;
67 | }
68 | case USER_PREFERENCE.SET_STORE_PATH:
69 | // @ts-ignore
70 | const storePath = payload?.data?.logStorePath
71 | if (storePath) {
72 | this.updateKeyInLogConfig("storePath", storePath)
73 | }
74 | break;
75 | case USER_PREFERENCE.SET_FILTER:
76 | // @ts-ignore
77 | const filter = payload?.data?.localLogFilterfilter
78 | if (filter) {
79 | this.updateKeyInLogConfig("filter", filter)
80 | }
81 | break;
82 |
83 | default:
84 | console.log("unexpected user preference action", type, payload)
85 | }
86 | };
87 | }
88 |
89 | export default UserPreferenceActionProcessor
--------------------------------------------------------------------------------
/electron-builder-setapp.json:
--------------------------------------------------------------------------------
1 | {
2 | "productName": "Requestly",
3 | "appId": "io.requestly.beta-setapp",
4 | "electronVersion": "23.0.0",
5 | "asar": true,
6 | "asarUnpack": "**\\*.{node,dll}",
7 | "files": [
8 | "dist",
9 | "node_modules",
10 | "package.json",
11 | "static"
12 | ],
13 | "protocols": {
14 | "name": "setapp-requestly-internal-protocol",
15 | "schemes": [
16 | "requestly-setapp"
17 | ]
18 | },
19 | "fileAssociations": [
20 | {
21 | "ext": "har",
22 | "name": "Network Captures",
23 | "role": "Default",
24 | "icon": "assets/icon.icns"
25 | },
26 | {
27 | "ext": "rqly",
28 | "name": "Requestly session",
29 | "role": "Owner",
30 | "icon": "assets/icon.icns"
31 | }
32 | ],
33 | "mac": {
34 | "type": "distribution",
35 | "hardenedRuntime": true,
36 | "entitlements": "assets/entitlements.mac.plist",
37 | "entitlementsInherit": "assets/entitlements.mac.plist",
38 | "gatekeeperAssess": false,
39 | "requirements": "assets/requirement.rqset",
40 | "identity": "Browserstack Inc (YQ5FZQ855D)",
41 | "notarize": {
42 | "teamId": "YQ5FZQ855D"
43 | },
44 | "minimumSystemVersion": "12.0",
45 | "x64ArchFiles": "**/static/nss/darwin/**",
46 | "icon": "assets/AppIcon.png",
47 | "target": [
48 | {
49 | "target": "zip",
50 | "arch": ["universal"]
51 | }
52 | ],
53 | "extraResources": [
54 | {
55 | "from": "assets/setappPublicKey.pem",
56 | "to": "setappPublicKey.pem"
57 | },
58 | {
59 | "from": "assets/AppIcon.png",
60 | "to": "AppIcon.png"
61 | }
62 | ],
63 | "extendInfo": {
64 | "CFBundleIconFile": "AppIcon.png",
65 | "NSUpdateSecurityPolicy": {
66 | "AllowProcesses": {
67 | "MEHY5QF425": [
68 | "com.setapp.DesktopClient.SetappAgent"
69 | ]
70 | }
71 | },
72 | "NSCameraUsageDescription": null,
73 | "NSMicrophoneUsageDescription": null,
74 | "NSBluetoothAlwaysUsageDescription": null,
75 | "NSBluetoothPeripheralUsageDescription": null
76 | },
77 | "files": [
78 | "!**/.git/*",
79 | "!**/.gitignore",
80 | "!**/.gitmodules",
81 | "!**/node_modules/@setapp/framework-wrapper/build/{Debug,Release}",
82 | "!**/node_modules/@setapp/framework-wrapper/{src,test}"
83 | ]
84 | },
85 | "dmg": {
86 | "sign": true,
87 | "contents": [
88 | {
89 | "x": 130,
90 | "y": 220
91 | },
92 | {
93 | "x": 410,
94 | "y": 220,
95 | "type": "link",
96 | "path": "/Applications"
97 | }
98 | ]
99 | },
100 | "directories": {
101 | "app": "release/app",
102 | "buildResources": "assets",
103 | "output": "release/build-setapp"
104 | },
105 | "extraResources": [
106 | "./assets/**",
107 | {
108 | "from": "./release/app",
109 | "filter": "static/**"
110 | }
111 | ],
112 | "publish": {
113 | "provider": "github",
114 | "releaseType": "release",
115 | "owner": "requestly",
116 | "repo": "requestly-desktop-app-release"
117 | }
118 | }
--------------------------------------------------------------------------------
/src/renderer/services/storage-cache.ts:
--------------------------------------------------------------------------------
1 | import { RQProxyProvider } from "@requestly/requestly-proxy";
2 | import { ipcRenderer } from "electron";
3 | import storageService from "lib/storage";
4 | import { STORE_NAME } from "lib/storage/constants";
5 | import ACTION_TYPES from "lib/storage/types/action-types";
6 | import startProxyServer from "renderer/actions/proxy/startProxyServer";
7 |
8 | class StorageCacheService {
9 | constructor() {
10 | this.init();
11 | }
12 |
13 | init = () => {
14 | this.updateCache(STORE_NAME.SSL_PROXYING);
15 | this.updateCache(STORE_NAME.USER_PREFERENCE);
16 | };
17 |
18 | updateCache = (storeName: string) => {
19 | if (!storeName) {
20 | console.log("Storage Name not provided");
21 | }
22 |
23 | switch (storeName) {
24 | case STORE_NAME.SSL_PROXYING:
25 | global.rq.sslProxyingStorage = storageService.processAction({
26 | type: ACTION_TYPES.SSL_PROXYING.GET_ALL,
27 | });
28 | console.log(`Updated ${storeName} cache`);
29 |
30 | // Hack: For timing out tunnel in case of inclusionList/ExclusionList Change
31 | this.destroySSLTunnels();
32 | break;
33 | case STORE_NAME.USER_PREFERENCE:
34 | const newUserPreferences = storageService.processAction({
35 | type: ACTION_TYPES.USER_PREFERENCE.GET_ALL,
36 | })
37 | global.rq.userPreferences = newUserPreferences
38 | console.log(`Updated ${storeName} cache`);
39 |
40 | // might not be necessary when other user preference attributes are added
41 | this.restartProxyServer(newUserPreferences?.defaultPort)
42 | break;
43 | default:
44 | console.log(`${storeName} cache not found`);
45 | }
46 | };
47 |
48 | getCache = (storeName: STORE_NAME) => {
49 | if (!storeName) {
50 | console.log("Storage Name not provided");
51 | }
52 |
53 | switch (storeName) {
54 | case STORE_NAME.SSL_PROXYING:
55 | return global.rq.sslProxyingStorage;
56 | case STORE_NAME.USER_PREFERENCE:
57 | return global.rq.userPreferences;
58 | default:
59 | console.log(`${storeName} cache not found`);
60 | }
61 |
62 | return;
63 | };
64 |
65 | destroySSLTunnels = () => {
66 | // Check if behvaiour breaks when working with multiple tunnels
67 | console.log("Destroying tunnels");
68 | console.log(global.rq.sslTunnelingSocketsMap);
69 | Object.values(global.rq.sslTunnelingSocketsMap || {}).forEach((socket) => {
70 | try {
71 | console.log("Tunnel destroyed");
72 | socket.destroy();
73 | } catch {
74 | console.log("Failed to destroy tunnel");
75 | }
76 | });
77 | global.rq.sslTunnelingSocketsMap = {};
78 | };
79 |
80 | restartProxyServer = async (port: number) => {
81 | // check to not trigger restart when proxy has not even started yet
82 | if(RQProxyProvider.rqProxyInstance){
83 | console.log("restarting proxy server on new port")
84 | const result = await startProxyServer(port, false)
85 | if(result.success) {
86 | ipcRenderer.invoke("proxy-restarted", {
87 | port: result.port,
88 | proxyIp: result.proxyIp
89 | })
90 | }
91 | }
92 | }
93 | }
94 |
95 | const storageCacheService = new StorageCacheService();
96 | export default storageCacheService;
97 |
--------------------------------------------------------------------------------
/src/renderer/utils/userPreferencesManager.js:
--------------------------------------------------------------------------------
1 | import Store from "electron-store";
2 |
3 | import { userPreferences } from "../config";
4 |
5 | class PreferenceManager {
6 | schema = {
7 | app_behaviour: {
8 | type: "object",
9 | properties: {
10 | [userPreferences.START_APP_ON_SYSTEM_STARTUP.key]: {
11 | type: "boolean",
12 | },
13 | [userPreferences.VISITOR_ID.key]: {
14 | type: "string",
15 | },
16 | },
17 | default: {
18 | [userPreferences.START_APP_ON_SYSTEM_STARTUP.key]:
19 | userPreferences.START_APP_ON_SYSTEM_STARTUP.defaultValue,
20 | [userPreferences.VISITOR_ID.key]:
21 | userPreferences.VISITOR_ID.defaultValue,
22 | },
23 | },
24 | proxy: {
25 | type: "object",
26 | properties: {
27 | [userPreferences.DEFAULT_PROXY_PORT.key]: {
28 | type: "number",
29 | },
30 | },
31 | default: {
32 | [userPreferences.DEFAULT_PROXY_PORT.key]:
33 | userPreferences.DEFAULT_PROXY_PORT.defaultValue,
34 | },
35 | },
36 | sentry: {
37 | type: "object",
38 | properties: {
39 | [userPreferences.ERROR_TRACKING_ENABLED.key]: {
40 | type: "boolean",
41 | },
42 | },
43 | default: {
44 | [userPreferences.ERROR_TRACKING_ENABLED.key]:
45 | userPreferences.ERROR_TRACKING_ENABLED.defaultValue,
46 | },
47 | },
48 | };
49 |
50 | constructor() {
51 | this.store = new Store({
52 | name: "UserPreferencesStorage",
53 | schema: this.schema,
54 | });
55 | }
56 |
57 | getPreferences = () => {
58 | return this.store.store;
59 | };
60 |
61 | setPreferences = (preference) => {
62 | this.store.store = preference;
63 | };
64 |
65 | getProxyDefaultHost = () => {
66 | return this.store.get(`proxy.${userPreferences.PROXY_HOST.key}`);
67 | };
68 |
69 | setProxyDefaultHost = (value) => {
70 | return this.store.set(`proxy.${userPreferences.PROXY_HOST.key}`, value);
71 | };
72 |
73 | getProxyDefaultPort = () => {
74 | return this.store.get(`proxy.${userPreferences.DEFAULT_PROXY_PORT.key}`);
75 | };
76 |
77 | setProxyDefaultPort = (value) => {
78 | return this.store.set(
79 | `proxy.${userPreferences.DEFAULT_PROXY_PORT.key}`,
80 | value
81 | );
82 | };
83 |
84 | getPreferenceStartAppOnSystemStartup = () => {
85 | return this.store.get(
86 | `app_behaviour.${userPreferences.START_APP_ON_SYSTEM_STARTUP.key}`
87 | );
88 | };
89 |
90 | setPreferenceStartAppOnSystemStartup = (value) => {
91 | return this.store.set(
92 | `app_behaviour.${userPreferences.START_APP_ON_SYSTEM_STARTUP.key}`,
93 | value
94 | );
95 | };
96 |
97 | getVisitorId = () => {
98 | return this.store.get(`app_behaviour.${userPreferences.VISITOR_ID.key}`);
99 | };
100 |
101 | setVisitorId = (value) => {
102 | return this.store.set(
103 | `app_behaviour.${userPreferences.VISITOR_ID.key}`,
104 | value
105 | );
106 | };
107 |
108 | resetPreferences = () => {
109 | this.store.reset("app_behaviour", "proxy", "certificate");
110 | };
111 | }
112 |
113 | const preferenceManager = new PreferenceManager();
114 |
115 | export default preferenceManager;
116 |
--------------------------------------------------------------------------------
/src/renderer/lib/proxy-interface/loggerService.js:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from "electron";
2 | import fs from "fs";
3 | import path from "path";
4 | import { getLocalFileLogConfig } from "renderer/actions/storage/cacheUtils";
5 | import logger from "utils/logger";
6 |
7 |
8 | function saveLogToLocalFile(log) {
9 | if(!log || log.requestState !== "COMPLETE") return;
10 |
11 | const logConfig = getLocalFileLogConfig();
12 |
13 | if(!logConfig) return;
14 |
15 | if (logConfig.isEnabled) {
16 | const logFilePath = getFilePathFromLogConfig(logConfig);
17 | if (logFilePath && doesLogMatchConfig(log, logConfig)) {
18 | addLogToFile(log, logFilePath);
19 | }
20 | }
21 |
22 | function doesLogMatchConfig(log, logConfig) {
23 | const logURL = getURLFromLog(log);
24 | if(logURL) {
25 | const configFilters = logConfig.filter;
26 | if (configFilters) {
27 | const isURLMatched = configFilters.some((filter) => {
28 | return logURL.includes(filter);
29 | });
30 | return isURLMatched;
31 | }
32 | }
33 | return false;
34 | function getURLFromLog(log) {
35 | const logEntry = log.finalHar?.log?.entries?.[0]
36 | if (logEntry) {
37 | const url = logEntry.request?.url;
38 | return url;
39 | }
40 | return null;
41 | }
42 | }
43 |
44 | function addLogToFile(log, filePath) {
45 | try {
46 | const logFileDir = path.dirname(filePath);
47 | if (!fs.existsSync(logFileDir)) {
48 | console.error("Log directory does not exist:", logFileDir);
49 | return;
50 | }
51 |
52 | fs.appendFile(filePath, `${JSON.stringify(log)} \n`, (err) => {
53 | if (err) {
54 | console.error("Error writing to log file:", err);
55 | }
56 | });
57 | } catch (error) {
58 | console.error("Error writing to log file:", error);
59 | }
60 | }
61 | }
62 |
63 | function getFilePathFromLogConfig(logConfig) {
64 | if (logConfig && logConfig.storePath) {
65 | const fileName = "interceptor_logs.jsonl";
66 | const filePath = logConfig.storePath + "/" + fileName;
67 | return filePath;
68 | }
69 | return null;
70 | }
71 |
72 | export function clearStoredLogs() {
73 | console.log("Clearing stored logs...");
74 | const logConfig = getLocalFileLogConfig();
75 | if (logConfig && logConfig.storePath) {
76 | const filePath = getFilePathFromLogConfig(logConfig);
77 | if (filePath && fs.existsSync(filePath)) {
78 | fs.truncate(filePath, 0, (err) => {
79 | if (err) {
80 | console.error("Error clearing log file:", err);
81 | } else {
82 | console.log("Log file cleared successfully.");
83 | }
84 | });
85 | }
86 | }
87 | }
88 |
89 | class LoggerService {
90 | addLog = (log, requestHeaders) => {
91 | try {
92 | // send log to webapp
93 | ipcRenderer.send("log-network-request-v2", log);
94 | } catch (error) {
95 | /* error only seen to happen during the app shutting down */
96 | logger.error("Error while sending network log - ", error)
97 | }
98 |
99 | try {
100 | // save to file if config is preset
101 | saveLogToLocalFile(log);
102 | } catch (error) {
103 | console.error("Error saving log to file:", error);
104 | }
105 | };
106 | }
107 |
108 | export default LoggerService;
109 |
--------------------------------------------------------------------------------
/src/lib/storage/action-processors/ssl-proxying.ts:
--------------------------------------------------------------------------------
1 | import ACTION_TYPES from "../types/action-types";
2 | import { StorageAction } from "../types/storage-action";
3 | import BaseActionProcessor from "./base";
4 | import StoreWrapper from "../store-wrapper";
5 | import { ISource } from "../types/ssl-proxying";
6 |
7 | class SSLProxyingActionProcessor extends BaseActionProcessor {
8 | constructor(store: StoreWrapper) {
9 | super(store);
10 | }
11 |
12 | process = ({ type, payload }: StorageAction) => {
13 | switch (type) {
14 | case ACTION_TYPES.SSL_PROXYING.ENABLE_ALL:
15 | this.store.set({ enabledAll: true });
16 | break;
17 | case ACTION_TYPES.SSL_PROXYING.DISABLE_ALL:
18 | this.store.set({ enabledAll: false });
19 | break;
20 | case ACTION_TYPES.SSL_PROXYING.UPSERT_INCLUSION_LIST_SOURCE:
21 | if (payload && payload.data && (payload.data as ISource).id) {
22 | this.store.set({
23 | [`inclusionList.${(payload.data as ISource).id}`]: payload.data,
24 | });
25 | }
26 | break;
27 | case ACTION_TYPES.SSL_PROXYING.DELETE_INCLUSION_LIST_SOURCE:
28 | if (payload && payload.id) {
29 | this.store.delete(`inclusionList.${payload.id}`);
30 | }
31 | break;
32 | case ACTION_TYPES.SSL_PROXYING.UPDATE_INCLUSION_LIST:
33 | if (payload && payload.data && payload.data) {
34 | const sourcesList = (payload.data || []) as ISource[];
35 | const inclusionListMap: { [id: string]: ISource } = {};
36 | sourcesList.forEach((source) => {
37 | inclusionListMap[source.id] = source;
38 | });
39 | this.store.set({ inclusionList: inclusionListMap });
40 | }
41 | break;
42 | case ACTION_TYPES.SSL_PROXYING.CLEAR_INCLUSION_LIST:
43 | this.store.set({ inclusionList: {} });
44 | break;
45 | case ACTION_TYPES.SSL_PROXYING.UPSERT_EXCLUSION_LIST_SOURCE:
46 | if (payload && payload.data && (payload.data as ISource).id) {
47 | this.store.set({
48 | [`exclusionList.${(payload.data as ISource).id}`]: payload.data,
49 | });
50 | }
51 | break;
52 | case ACTION_TYPES.SSL_PROXYING.DELETE_EXCLUSION_LIST_SOURCE:
53 | if (payload && payload.id) {
54 | this.store.delete(`exclusionList.${payload.id}`);
55 | }
56 | break;
57 | case ACTION_TYPES.SSL_PROXYING.UPDATE_EXCLUSION_LIST:
58 | if (payload && payload.data && payload.data) {
59 | const sourcesList = (payload.data || []) as ISource[];
60 | const exclusionListMap: { [id: string]: ISource } = {};
61 | sourcesList.forEach((source) => {
62 | exclusionListMap[source.id] = source;
63 | });
64 | this.store.set({ exclusionList: exclusionListMap });
65 | }
66 | break;
67 | case ACTION_TYPES.SSL_PROXYING.CLEAR_EXCLUSION_LIST:
68 | this.store.set({ exclusionList: {} });
69 | break;
70 | case ACTION_TYPES.SSL_PROXYING.GET_ALL:
71 | return this.store.getAll();
72 | case ACTION_TYPES.SSL_PROXYING.GET_INCLUSION_LIST:
73 | const inclusionListMap = this.store.get("inclusionList") || {};
74 | return Object.values(inclusionListMap);
75 | case ACTION_TYPES.SSL_PROXYING.GET_EXCLUSION_LIST:
76 | const exclusionListMap = this.store.get("exclusionList") || {};
77 | return Object.values(exclusionListMap);
78 | default:
79 | console.log("Nothing in SSLProxying action processor");
80 | }
81 | };
82 | }
83 |
84 | export default SSLProxyingActionProcessor;
85 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/mobile/iosSimulator.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | /* eslint-disable no-underscore-dangle */
3 | import { SystemWideProxy } from "../os/system-wide";
4 | import { Simctl } from "node-simctl";
5 |
6 | export default class IosSimulatorDevice extends SystemWideProxy {
7 | static runningSimulators = {};
8 |
9 | static potentialSimulators = {};
10 |
11 | static simctl = new Simctl();
12 |
13 | static simulatorsBeingIntercepted = {};
14 |
15 | constructor(config) {
16 | super(config);
17 | this.id = "ios-simulator";
18 | this.config = config;
19 | console.log("IosSimulator", { config });
20 | this.simctl = new Simctl();
21 | }
22 |
23 | async isActive() {
24 | if (Object.keys(IosSimulatorDevice.simulatorsBeingIntercepted).length)
25 | return true;
26 | return false;
27 | }
28 |
29 | async isActivable() {
30 | if (process.platform !== "darwin") return false;
31 | return true;
32 | }
33 |
34 | async activate(proxyPort, options) {
35 | const deviceIds = // if no deviceIds provided, activate all running simulators
36 | options?.deviceIds ||
37 | Object.keys(IosSimulatorDevice.runningSimulators).map((simId) => simId);
38 |
39 | deviceIds.forEach(async (deviceId) => {
40 | try {
41 | // sanity check
42 | await this.launchDevice(deviceId);
43 | } catch (e) {
44 | console.log(
45 | "Error occured while launching device. If because device was already running, ignore"
46 | );
47 | console.log(e);
48 | }
49 | await this.trustRootCAForDevice(deviceId).catch(console.error);
50 | IosSimulatorDevice.simulatorsBeingIntercepted[deviceId] = true;
51 | });
52 |
53 | super.activate(proxyPort);
54 | }
55 |
56 | async deactivate(proxyPort, options) {
57 | const deviceIds = // if no deviceIds provided, activate all running simulators
58 | options?.deviceIds ||
59 | Object.keys(IosSimulatorDevice.simulatorsBeingIntercepted).map(
60 | (simId) => simId
61 | );
62 |
63 | deviceIds.forEach(async (deviceId) => {
64 | console.log("DBG: Deactivating", deviceId);
65 | delete IosSimulatorDevice.simulatorsBeingIntercepted[deviceId];
66 | });
67 | return super.deactivate();
68 | }
69 |
70 | async trustRootCAForDevice(udid) {
71 | IosSimulatorDevice.simctl._udid = udid;
72 | return IosSimulatorDevice.simctl.addRootCertificate(
73 | this.config.https.certPath
74 | );
75 | }
76 |
77 | async launchDevice(udid) {
78 | this.simctl._udid = udid;
79 | return this.simctl.bootDevice();
80 | }
81 |
82 | static async getAvailableSimulators() {
83 | const devices = await this.simctl.list();
84 | const allDevices = Object.values(devices.devices).flat();
85 | const activeDevices = allDevices.filter(
86 | (device) => device.state === "Booted"
87 | );
88 | const usedDevices = allDevices.filter((device) => device.logPathSize > 0);
89 |
90 | const result = {
91 | allDevices: this.convertDeviceArrayToMap(allDevices),
92 | activeDevices: this.convertDeviceArrayToMap(activeDevices),
93 | usedDevices: this.convertDeviceArrayToMap(usedDevices),
94 | };
95 |
96 | IosSimulatorDevice.runningSimulators = result.activeDevices;
97 | IosSimulatorDevice.potentialSimulators = result.allDevices;
98 | return result;
99 | }
100 |
101 | static convertDeviceArrayToMap(devices) {
102 | return devices.reduce((acc, device) => {
103 | acc[device.udid] = device;
104 | return acc;
105 | }, {});
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/renderer/actions/apps/browsers/browser-handler.js:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | const { promisify } = require("es6-promisify");
3 | import * as getBrowserLauncherCbObject from "@httptoolkit/browser-launcher";
4 | import {
5 | LaunchOptions,
6 | BrowserInstance,
7 | Browser,
8 | update as updateBrowserCacheCb,
9 | } from "@httptoolkit/browser-launcher";
10 | // ACTIONS
11 | import { readFile, deleteFile } from "../../fileManagement";
12 | // UTILS
13 | import { delay } from "../../../utils/misc";
14 | import * as _ from "lodash";
15 | // SENTRY
16 | import * as Sentry from "@sentry/browser";
17 |
18 | const getBrowserLauncherCb = getBrowserLauncherCbObject.default;
19 |
20 | const getBrowserLauncher = promisify(getBrowserLauncherCb);
21 | const updateBrowserCache = promisify(updateBrowserCacheCb);
22 |
23 | const browserConfigPath = (configPath) =>
24 | path.join(configPath, "browsers.json");
25 |
26 | export { BrowserInstance, Browser };
27 |
28 | export const checkBrowserConfig = async (configPath) => {
29 | // It's not clear why, but sometimes the browser config can become corrupted, so it's not valid JSON
30 | // If that happens browser-launcher can hit issues. To avoid that entirely, we check it here on startup.
31 | const browserConfig = browserConfigPath(configPath);
32 | try {
33 | const rawConfig = await readFile(browserConfig, "utf8");
34 | JSON.parse(rawConfig);
35 | } catch (error) {
36 | Sentry.captureException(error);
37 | if (error.code === "ENOENT") return;
38 | console.warn(
39 | `Failed to read browser config cache from ${browserConfig}, clearing.`,
40 | error
41 | );
42 | return deleteFile(browserConfig).catch((err) => {
43 | Sentry.captureException(err);
44 | // There may be possible races around here - as long as the file's gone, we're happy
45 | if (err.code === "ENOENT") return;
46 | console.error("Failed to clear broken config file:", err);
47 | });
48 | }
49 | };
50 |
51 | let launcher;
52 |
53 | export const getLauncher = async (configPath) => {
54 | if (!launcher) {
55 | const browserConfig = browserConfigPath(configPath);
56 | launcher = getBrowserLauncher(browserConfig);
57 | launcher.then(async () => {
58 | // Async after first creating the launcher, we trigger a background cache update.
59 | // This can be *synchronously* expensive (spawns 10s of procs, 10+ms sync per
60 | // spawn on unix-based OSs) so defer briefly.
61 | await delay(2000);
62 | try {
63 | await updateBrowserCache(browserConfig);
64 | console.log("Browser cache updated");
65 | // Need to reload the launcher after updating the cache:
66 | launcher = getBrowserLauncher(browserConfig);
67 | } catch (e) {
68 | Sentry.captureException(e);
69 | console.log(e);
70 | }
71 | });
72 | // Reset & retry if this fails somehow:
73 | launcher.catch((e) => {
74 | Sentry.captureException(e);
75 | launcher = undefined;
76 | });
77 | }
78 | return launcher;
79 | };
80 |
81 | export const getAvailableBrowsers = async (configPath) => {
82 | return (await getLauncher(configPath)).browsers;
83 | };
84 |
85 | export { LaunchOptions };
86 |
87 | export const launchBrowser = async (url, options, configPath) => {
88 | const launcher = await getLauncher(configPath);
89 | const browserInstance = await promisify(launcher)(url, options);
90 | browserInstance.process.on("error", (e) => {
91 | // If nothing else is listening for this error, this acts as default
92 | // fallback error handling: log & report & don't crash.
93 | if (browserInstance.process.listenerCount("error") === 1) {
94 | console.log("Browser launch error");
95 | }
96 | });
97 | return browserInstance;
98 | };
99 |
--------------------------------------------------------------------------------
/release/app/static/overrides/js/prepend-node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * --require'd by node before loading any other modules. The --require
3 | * option is injected both using NODE_OPTIONS and with a node wrapper in
4 | * PATH to handle various potential cases (node dedupes --require anyway).
5 | *
6 | * This file sets up a global agent for the http & https modules,
7 | * plus tweaks various other HTTP clients that need nudges, so they
8 | * all correctly pick up the proxy from the environment.
9 | *
10 | * Tested against Node 6, 8, 10, 12 and 14.
11 | */
12 |
13 | const wrapModule = require("./wrap-require");
14 |
15 | let httpAlreadyIntercepted = false;
16 |
17 | function interceptAllHttp() {
18 | if (httpAlreadyIntercepted) return;
19 | httpAlreadyIntercepted = true;
20 |
21 | const MAJOR_NODEJS_VERSION = parseInt(
22 | process.version.slice(1).split(".")[0],
23 | 10
24 | );
25 |
26 | if (MAJOR_NODEJS_VERSION >= 10) {
27 | // `global-agent` works with Node.js v10 and above.
28 | const globalAgent = require("global-agent");
29 | globalAgent.bootstrap();
30 | } else {
31 | // `global-tunnel-ng` works only with Node.js v10 and below.
32 | const globalTunnel = require("global-tunnel-ng");
33 | globalTunnel.initialize();
34 | }
35 | }
36 |
37 | wrapModule("http", interceptAllHttp, true);
38 | wrapModule("https", interceptAllHttp, true);
39 |
40 | wrapModule("axios", function wrapAxios(loadedModule) {
41 | // Global agent handles this automatically, if used (i.e. Node >= 10)
42 | if (global.GLOBAL_AGENT) return;
43 |
44 | // Disable built-in proxy support, to let global-tunnel take precedence
45 | // Supported back to the very first release of Axios
46 | loadedModule.defaults.proxy = false;
47 | });
48 |
49 | wrapModule("request", function wrapRequest(loadedModule) {
50 | // Global agent handles this automatically, if used (i.e. Node >= 10)
51 | if (global.GLOBAL_AGENT) return;
52 |
53 | // Is this Request >= 2.17?
54 | // Before then proxy support isn't a problem anyway
55 | if (!loadedModule.defaults) return;
56 |
57 | // Have we intercepted this already?
58 | if (loadedModule.INTERCEPTED_BY_HTTPTOOLKIT) return;
59 |
60 | const fixedModule = loadedModule.defaults({ proxy: false });
61 | fixedModule.INTERCEPTED_BY_HTTPTOOLKIT = true;
62 | return fixedModule;
63 | });
64 |
65 | wrapModule("superagent", function wrapSuperagent(loadedModule) {
66 | // Global agent handles this automatically, if used (i.e. Node >= 10)
67 | if (global.GLOBAL_AGENT) return;
68 |
69 | // Have we intercepted this already?
70 | if (loadedModule.INTERCEPTED_BY_HTTPTOOLKIT) return;
71 | loadedModule.INTERCEPTED_BY_HTTPTOOLKIT = true;
72 |
73 | // Global tunnel doesn't successfully reconfigure superagent.
74 | // To fix it, we forcibly override the agent property on every request.
75 | const originalRequestMethod = loadedModule.Request.prototype.request;
76 | loadedModule.Request.prototype.request = function () {
77 | if (this.url.indexOf("https:") === 0) {
78 | this._agent = require("https").globalAgent;
79 | } else {
80 | this._agent = require("http").globalAgent;
81 | }
82 | return originalRequestMethod.apply(this, arguments);
83 | };
84 | });
85 |
86 | wrapModule("stripe", function wrapStripe(loadedModule) {
87 | if (loadedModule.INTERCEPTED_BY_HTTPTOOLKIT) return;
88 |
89 | return Object.assign(
90 | function () {
91 | const result = loadedModule.apply(this, arguments);
92 |
93 | // Set by global-tunnel in Node < 10 (or global-agent in 11.7+)
94 | result.setHttpAgent(require("https").globalAgent);
95 | return result;
96 | },
97 | loadedModule,
98 | { INTERCEPTED_BY_HTTPTOOLKIT: true }
99 | );
100 | });
101 |
--------------------------------------------------------------------------------
/src/lib/autoupdate/index.js:
--------------------------------------------------------------------------------
1 | import { autoUpdater } from "electron-updater";
2 | import { ipcMain } from "electron";
3 | import log from "electron-log";
4 |
5 | // I hope you know what you are doing here
6 | //
7 | // :::!~!!!!!:.
8 | // .xUHWH!! !!?M88WHX:.
9 | // .X*#M@$!! !X!M$$$$$$WWx:.
10 | // :!!!!!!?H! :!$!$$$$$$$$$$8X:
11 | // !!~ ~:~!! :~!$!#$$$$$$$$$$8X:
12 | // :!~::!H!< ~.U$X!?R$$$$$$$$MM!
13 | // ~!~!!!!~~ .:XW$$$U!!?$$$$$$RMM!
14 | // !:~~~ .:!M"T#$$$$WX??#MRRMMM!
15 | // ~?WuxiW*` `"#$$$$8!!!!??!!!
16 | // :X- M$$$$ `"T#$T~!8$WUXU~
17 | // :%` ~#$$$m: ~!~ ?$$$$$$
18 | // :!`.- ~T$$$$8xx. .xWW- ~""##*"
19 | // ..... -~~:<` ! ~?T#$$@@W@*?$$ /`
20 | // W$@@M!!! .!~~ !! .:XUW$W!~ `"~: :
21 | // #"~~`.:x%`!! !H: !WM$$$$Ti.: .!WUn+!`
22 | // :::~:!!`:X~ .: ?H.!u "$$$B$$$!W:U!T$$M~
23 | // .~~ :X@!.-~ ?@WTWo("*$$$W$TH$! `
24 | // Wi.~!X$?!-~ : ?$$$B$Wu("**$RM!
25 | // $R@i.~~ ! : ~$$$$$B$$en:``
26 | // ?MXT@Wx.~ : ~"##*$$$$M~
27 | class AutoUpdate {
28 | constructor(webAppWindow) {
29 | this.webAppWindow = webAppWindow;
30 |
31 | log.transports.file.level = 'verbose';
32 | autoUpdater.logger = log;
33 | // Now this is triggered after rendering UI
34 | // autoUpdater.checkForUpdatesAndNotify();
35 | this.updateAvailable = false;
36 | this.availableUpdateDetails = {};
37 | this.updateDownloaded = false;
38 | this.downloadedUpdateDetails = {};
39 |
40 | this.init_events();
41 | }
42 |
43 | init_events = () => {
44 | autoUpdater.on("update-available", (updateInfo) => {
45 | log.info("update available", updateInfo);
46 | this.updateAvailable = true;
47 | this.availableUpdateDetails = updateInfo;
48 |
49 | if (this.webAppWindow && this.webAppWindow.webContents)
50 | this.webAppWindow.webContents.send("update-available", updateInfo);
51 | });
52 |
53 | autoUpdater.on("checking-for-update", () => {
54 | log.info("checking-for-update");
55 | });
56 |
57 | autoUpdater.on("update-not-available", () => {
58 | log.info("update-not-available");
59 | });
60 |
61 | autoUpdater.on("download-progress", (progressObj) => {
62 | if (this.webAppWindow && this.webAppWindow.webContents) {
63 | this.webAppWindow.webContents.send("download-progress", progressObj);
64 | }
65 | });
66 |
67 | autoUpdater.on("update-downloaded", (updateInfo) => {
68 | log.info("update downloaded", updateInfo)
69 | this.updateDownloaded = true;
70 | this.downloadedUpdateDetails = updateInfo;
71 |
72 | if (this.webAppWindow && this.webAppWindow.webContents) {
73 | this.webAppWindow.webContents.send("update-downloaded", updateInfo);
74 | }
75 | });
76 |
77 | autoUpdater.on("err", (err)=> {
78 | log.error("error received on autoupdater", err);
79 | })
80 |
81 | autoUpdater.on("before-quit-for-update", (info) => {
82 | log.info("before-quit-for-update event triggered", info)
83 | });
84 |
85 | autoUpdater.on("before-quit", (info) => {
86 | log.info("before-quit event triggered", info)
87 | });
88 |
89 | ipcMain.handle("check-for-updates-and-notify", () => {
90 | autoUpdater.checkForUpdatesAndNotify();
91 | });
92 |
93 | ipcMain.handle("quit-and-install", () => {
94 | log.info("recieved quit and install")
95 | global.quitAndInstall = true;
96 | const res = autoUpdater.quitAndInstall();
97 | log.info("finished quit and install", res)
98 | });
99 | };
100 | }
101 |
102 | export default AutoUpdate;
103 |
--------------------------------------------------------------------------------
/src/renderer/actions/local-sync/fs/fs.service.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { Abortable } from "node:events";
3 | import fs, {
4 | Mode,
5 | ObjectEncodingOptions,
6 | OpenMode,
7 | PathLike,
8 | PathOrFileDescriptor,
9 | StatOptions,
10 | } from "node:fs";
11 | import fsp, { FileHandle } from "node:fs/promises";
12 | import { Stream } from "node:stream";
13 | import { AccessFallback } from "./access-fallback.decorator";
14 | import { SudoCommandExecutor } from "./sudoCommandExecutor";
15 |
16 | export class FsService {
17 | static readFileSync(
18 | path: PathOrFileDescriptor,
19 | options?: {
20 | encoding?: null | undefined;
21 | flag?: string | undefined;
22 | } | null
23 | ) {
24 | return fs.readFileSync(path, options);
25 | }
26 |
27 | static readFile(
28 | path: PathLike | FileHandle,
29 | options?:
30 | | ({
31 | encoding?: null | undefined;
32 | flag?: OpenMode | undefined;
33 | } & Abortable)
34 | | null
35 | ): Promise {
36 | return fsp.readFile(path, options);
37 | }
38 |
39 | @AccessFallback(SudoCommandExecutor.readFile)
40 | static readFileWithElevatedAccess(
41 | ...params: Parameters
42 | ) {
43 | return FsService.readFile(...params);
44 | }
45 |
46 | static readdir(
47 | path: PathLike,
48 | options?:
49 | | (ObjectEncodingOptions & {
50 | withFileTypes?: false | undefined;
51 | })
52 | | BufferEncoding
53 | | null
54 | ) {
55 | return fsp.readdir(path, options);
56 | }
57 |
58 | static stat(
59 | path: PathLike,
60 | opts?: StatOptions & {
61 | bigint?: false | undefined;
62 | }
63 | ) {
64 | return fsp.stat(path, opts);
65 | }
66 |
67 | static lstat(
68 | path: PathLike,
69 | opts?: StatOptions & {
70 | bigint?: false | undefined;
71 | }
72 | ) {
73 | return fsp.lstat(path, opts);
74 | }
75 |
76 | static writeFile(
77 | file: PathLike | FileHandle,
78 | data:
79 | | string
80 | | NodeJS.ArrayBufferView
81 | | Iterable
82 | | AsyncIterable
83 | | Stream,
84 | options?:
85 | | (ObjectEncodingOptions & {
86 | mode?: Mode | undefined;
87 | flag?: OpenMode | undefined;
88 | } & Abortable)
89 | | BufferEncoding
90 | | null
91 | ) {
92 | return fsp.writeFile(file, data, options);
93 | }
94 |
95 | @AccessFallback(SudoCommandExecutor.writeFile)
96 | static writeFileWithElevatedAccess(
97 | ...params: Parameters
98 | ) {
99 | return FsService.writeFile(...params);
100 | }
101 |
102 | static unlink(...params: Parameters) {
103 | return fsp.unlink(...params);
104 | }
105 |
106 | static rm(
107 | path: PathLike,
108 | options?: fs.RmOptions
109 | ) {
110 | return fsp.rm(path, options);
111 | }
112 |
113 | static mkdir(...params: Parameters) {
114 | return fsp.mkdir(...params);
115 | }
116 |
117 | @AccessFallback(SudoCommandExecutor.mkdir)
118 | static mkdirWithElevatedAccess(...params: Parameters) {
119 | return FsService.mkdir(...params);
120 | }
121 |
122 | static rmdir(...params: Parameters) {
123 | return fsp.rmdir(...params);
124 | }
125 |
126 | static rename(...params: Parameters) {
127 | return fsp.rename(...params);
128 | }
129 |
130 | static cp(...params: Parameters) {
131 | return fsp.cp(...params);
132 | }
133 |
134 | static existsSync(path: PathLike) {
135 | return fs.existsSync(path);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/actions/networkSessionStorage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-use-before-define */
2 | import path from "path";
3 | import fs from "fs";
4 | import { randomUUID } from "crypto";
5 | import { app } from "electron";
6 |
7 | const NETWORK_SESSIONS_FOLDER_NAME = "network-sessions";
8 |
9 | function getSessionStorageFolderPath() {
10 | const folderPath = path.join(
11 | app.getPath("appData"),
12 | NETWORK_SESSIONS_FOLDER_NAME
13 | );
14 |
15 | if (!fs.existsSync(folderPath)) {
16 | fs.mkdirSync(folderPath);
17 | }
18 | return folderPath;
19 | }
20 |
21 | export async function getAllNetworkSessions() {
22 | let finalFilesList = [];
23 | const folderPath = getSessionStorageFolderPath();
24 |
25 | const sessionsFileRegexPattern = /(.+)_(.+)_(\d+).har/;
26 |
27 | const files = await fs.promises.readdir(folderPath);
28 | finalFilesList = files
29 | .filter((file) => {
30 | return sessionsFileRegexPattern.test(file);
31 | })
32 | .map((file) => {
33 | const [fileName, id, name, createdTs] = file.match(
34 | sessionsFileRegexPattern
35 | );
36 | return { id, name, ts: parseInt(createdTs, 10), fileName };
37 | });
38 | return finalFilesList;
39 | }
40 |
41 | async function getSessionPath(id, name, ts) {
42 | const sessionsPath = getSessionStorageFolderPath();
43 | if (!name) {
44 | const matchingFiles = (await getAllNetworkSessions()).filter(
45 | ({ fileName }) => {
46 | return fileName?.startsWith(id);
47 | }
48 | );
49 |
50 | if (matchingFiles.length) {
51 | return path.join(sessionsPath, matchingFiles[0].fileName);
52 | }
53 |
54 | return null;
55 | }
56 |
57 | if (ts) {
58 | return path.join(sessionsPath, `${id}_${name}_${ts}.har`);
59 | }
60 |
61 | return path.join(path.format(sessionsPath), `${id}_${name}.har`);
62 | }
63 |
64 | export function getMetadataFromPath(pathString) {
65 | const parsedFilePath = path.parse(pathString);
66 | const [id, name, createdTs] = parsedFilePath.name.split("_");
67 | return { id, name, createdTs, fileName: parsedFilePath.base };
68 | }
69 |
70 | export async function storeSessionRecording(har, name, originalFilePath) {
71 | const id = randomUUID();
72 | const createdTs = Date.now();
73 | const sessionPath = await getSessionPath(id, name, createdTs);
74 |
75 | if (originalFilePath) linkHarSessionToOriginalFile();
76 | else createHarSessionFile();
77 |
78 | function createHarSessionFile() {
79 | fs.writeFileSync(sessionPath, JSON.stringify(har));
80 | }
81 |
82 | function linkHarSessionToOriginalFile() {
83 | fs.symlink(originalFilePath, sessionPath, (err) => {
84 | if (err) {
85 | console.error("Error creating symlink to original file: ", err);
86 | console.log("creating copy instead");
87 | createHarSessionFile();
88 | }
89 | });
90 | }
91 |
92 | return id;
93 | }
94 |
95 | export async function deleteNetworkRecording(id) {
96 | const sessionPath = await getSessionPath(id);
97 | return fs.unlink(sessionPath, () => {});
98 | }
99 |
100 | async function readSessionFile(sessionPath, id, metadata) {
101 | try {
102 | const data = await fs.promises.readFile(sessionPath, "utf8");
103 | return { ...metadata, har: JSON.parse(data) };
104 | } catch (err) {
105 | if (err.code === "ENOENT") {
106 | console.error(`session for ${id} does not exist`);
107 | } else {
108 | console.error(err);
109 | }
110 | return null;
111 | }
112 | }
113 |
114 | export async function getSessionRecording(id) {
115 | const sessionPath = await getSessionPath(id);
116 | if (sessionPath) {
117 | const metadata = getMetadataFromPath(sessionPath);
118 | const result = await readSessionFile(sessionPath, id, metadata);
119 | return result;
120 | }
121 | return null;
122 | }
123 |
--------------------------------------------------------------------------------