├── .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 | --------------------------------------------------------------------------------