├── website ├── public │ └── .gitkeep ├── src │ └── public │ │ ├── favicon.ico │ │ ├── assets │ │ ├── images │ │ │ ├── add_rule.png │ │ │ ├── listen_requests.png │ │ │ ├── inspect_response.png │ │ │ └── intercept_requests.png │ │ ├── svg │ │ │ ├── twitter-brands.svg │ │ │ └── github-brands.svg │ │ └── styles │ │ │ ├── faq.css │ │ │ └── index.css │ │ ├── index.html │ │ └── faq.html ├── README.md ├── .editorconfig ├── .gitlab-ci.yml ├── .gitignore ├── interceptor.svg ├── package.json ├── LICENSE └── .gitattributes ├── .npmrc ├── web-ext-artifacts └── .gitkeep ├── .npmignore ├── .prettierrc ├── app ├── images │ ├── icon.png │ ├── icon-16.png │ ├── icon-48.png │ ├── icon-128.png │ ├── icon-disabled.png │ └── icon-disabled-16.png ├── stylesheets │ ├── fonts │ │ ├── 7cHpv4kjgoGqM7E_DMs5ynghnQ.woff2 │ │ ├── 7cHpv4kjgoGqM7E_Ass5ynghnQci.woff2 │ │ ├── 7cHqv4kjgoGqM7E30-8s51ostz0rdg.woff2 │ │ └── 7cHqv4kjgoGqM7E30-8s6Vostz0rdom9.woff2 │ ├── _grid.css │ ├── _header.css │ ├── _variables.css │ ├── _reset.css │ ├── _toggleSwitch.css │ ├── styles.css │ ├── _form.css │ └── _table.css ├── __tests__ │ ├── setup.ts │ ├── intercept_all.test.jsx │ ├── request_list.test.jsx │ ├── AddRuleModal.test.jsx │ ├── intercept_box.test.jsx │ └── popup.test.jsx ├── __mocks__ │ └── message_service.ts ├── utils │ └── utils.ts ├── components │ ├── Icons │ │ ├── PlayIcon.tsx │ │ └── StopIcon.tsx │ ├── ModalWrapper │ │ └── index.tsx │ ├── InterceptAllButton.tsx │ ├── Intercept_Components │ │ ├── index.tsx │ │ ├── RequestHeaderList.tsx │ │ └── InterceptTextBox.tsx │ ├── Logo.tsx │ ├── Switch.tsx │ ├── AddRuleModal.tsx │ └── RequestList.tsx ├── actions │ ├── addRequest.ts │ └── index.ts ├── index.html ├── store │ ├── popup_store.ts │ └── aliases.ts ├── containers │ ├── app.tsx │ └── Popup.tsx ├── manifest.json ├── reducers │ ├── addRequest.ts │ └── rootReducer.ts ├── message_service.ts ├── types │ └── index.ts ├── background │ └── background.ts └── content │ └── content.ts ├── images ├── interceptor_ui.png ├── intercept_success.png ├── interceptor_disabled.png ├── intercept_multiple_xhr.png ├── interceptor_textfields.png ├── interceptor_ui_xhr_list.png ├── interceptor_modifyresponse.png └── interceptor_showresponse.png ├── .editorconfig ├── .gitignore ├── .travis.yml ├── .github ├── workflows │ └── nodejs.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── Privacy-policy.md ├── tsconfig.json ├── LICENSE ├── webpack.config.prod.js ├── webpack.config.dev.js ├── CHANGELOG.md ├── package.json ├── gulpfile.js └── README.md /website/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /web-ext-artifacts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | npm-debug.log 4 | ._* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "parser": "typescript", 4 | "tabWidth": 2 5 | } 6 | -------------------------------------------------------------------------------- /app/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon.png -------------------------------------------------------------------------------- /app/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon-16.png -------------------------------------------------------------------------------- /app/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon-48.png -------------------------------------------------------------------------------- /app/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon-128.png -------------------------------------------------------------------------------- /images/interceptor_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_ui.png -------------------------------------------------------------------------------- /app/images/icon-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon-disabled.png -------------------------------------------------------------------------------- /images/intercept_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/intercept_success.png -------------------------------------------------------------------------------- /app/images/icon-disabled-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/images/icon-disabled-16.png -------------------------------------------------------------------------------- /images/interceptor_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_disabled.png -------------------------------------------------------------------------------- /website/src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/website/src/public/favicon.ico -------------------------------------------------------------------------------- /images/intercept_multiple_xhr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/intercept_multiple_xhr.png -------------------------------------------------------------------------------- /images/interceptor_textfields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_textfields.png -------------------------------------------------------------------------------- /images/interceptor_ui_xhr_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_ui_xhr_list.png -------------------------------------------------------------------------------- /images/interceptor_modifyresponse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_modifyresponse.png -------------------------------------------------------------------------------- /images/interceptor_showresponse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/images/interceptor_showresponse.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailling_whitespace = true 8 | -------------------------------------------------------------------------------- /website/src/public/assets/images/add_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/website/src/public/assets/images/add_rule.png -------------------------------------------------------------------------------- /website/src/public/assets/images/listen_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/website/src/public/assets/images/listen_requests.png -------------------------------------------------------------------------------- /app/stylesheets/fonts/7cHpv4kjgoGqM7E_DMs5ynghnQ.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/stylesheets/fonts/7cHpv4kjgoGqM7E_DMs5ynghnQ.woff2 -------------------------------------------------------------------------------- /website/src/public/assets/images/inspect_response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/website/src/public/assets/images/inspect_response.png -------------------------------------------------------------------------------- /app/__tests__/setup.ts: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import * as Adapter from 'enzyme-adapter-react-16'; 3 | 4 | 5 | configure({ adapter: new Adapter() }); 6 | -------------------------------------------------------------------------------- /app/stylesheets/fonts/7cHpv4kjgoGqM7E_Ass5ynghnQci.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/stylesheets/fonts/7cHpv4kjgoGqM7E_Ass5ynghnQci.woff2 -------------------------------------------------------------------------------- /website/src/public/assets/images/intercept_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/website/src/public/assets/images/intercept_requests.png -------------------------------------------------------------------------------- /app/stylesheets/fonts/7cHqv4kjgoGqM7E30-8s51ostz0rdg.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/stylesheets/fonts/7cHqv4kjgoGqM7E30-8s51ostz0rdg.woff2 -------------------------------------------------------------------------------- /app/stylesheets/fonts/7cHqv4kjgoGqM7E30-8s6Vostz0rdom9.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemancers/interceptor/HEAD/app/stylesheets/fonts/7cHqv4kjgoGqM7E30-8s6Vostz0rdom9.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /dist 4 | .DS_Store 5 | */.DS_Store 6 | */*/.DS_Store 7 | package-lock.json 8 | /.vscode 9 | .env 10 | web-ext-artifacts/* 11 | !web-ext-artifacts/.gitkeep 12 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Interceptor Landing Page 2 | 3 | ## Development 4 | 5 | Clone the repo and run 6 | 7 | ``` 8 | npm install 9 | npm start 10 | ``` 11 | 12 | ## Production build 13 | 14 | ``` 15 | npm run build 16 | ``` 17 | -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /website/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:alpine 2 | 3 | pages: 4 | cache: 5 | paths: 6 | - node_modules/ 7 | 8 | stage: deploy 9 | script: 10 | - npm install 11 | - npm run build 12 | artifacts: 13 | paths: 14 | - public 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - "$HOME/.nvm" 6 | 7 | matrix: 8 | fast_finish: true 9 | include: 10 | - node_js: 12 11 | script: 12 | - yarn test 13 | - node_js: 10 14 | script: 15 | - yarn test 16 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules/ 8 | 9 | # Optional npm cache directory 10 | .npm 11 | 12 | # parcel-bundler cache (https://parceljs.org/) 13 | .cache 14 | 15 | #Dist 16 | dist/ 17 | public/ 18 | !public/.gitkeep 19 | -------------------------------------------------------------------------------- /app/__mocks__/message_service.ts: -------------------------------------------------------------------------------- 1 | export const getRequests = jest.fn((tabId:number, callback) => { 2 | callback(); 3 | }); 4 | 5 | export const enableLogging = jest.fn(); 6 | export const clearData = jest.fn(); 7 | export const disableLogging = jest.fn(); 8 | export const getEnabledStatus = jest.fn((tabId:number, callback) => { 9 | callback() 10 | }); 11 | -------------------------------------------------------------------------------- /app/utils/utils.ts: -------------------------------------------------------------------------------- 1 | setTimeout(() => { 2 | const bodyElement = document.querySelector("body"); 3 | const style: CSSStyleDeclaration = bodyElement.style; 4 | style.display = "block"; 5 | setTimeout(() => { 6 | style.opacity = "1"; 7 | }); 8 | }, 200); 9 | 10 | // Hack to fix Randomly Appearing Extension popup position issue - 11 | // https://bugs.chromium.org/p/chromium/issues/detail?id=428044 12 | -------------------------------------------------------------------------------- /app/components/Icons/PlayIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | export const PlayIcon = props => ( 3 | 15 | ); 16 | -------------------------------------------------------------------------------- /app/components/ModalWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | export const Modal = ({ modalTitle, handleClose, children }) => { 3 | return ( 4 |
5 |
6 |
7 | 10 | × 11 | 12 |

{modalTitle}

13 |
14 | {children} 15 |
16 |
17 | ); 18 | }; 19 | 20 | Modal.displayName = "Modal"; 21 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: yarn install, build, and test 21 | run: | 22 | yarn 23 | yarn run build --if-present 24 | yarn test 25 | env: 26 | CI: true -------------------------------------------------------------------------------- /app/components/Icons/StopIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | export const StopIcon = props => ( 3 | 15 | ); 16 | -------------------------------------------------------------------------------- /website/interceptor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interceptor-landing", 3 | "version": "1.0.0", 4 | "homepage": "https://codemancers.gitlab.io/interceptor-landing/", 5 | "description": "Interceptor Landing Page", 6 | "author": "Amit Bhavikatti", 7 | "license": "MIT", 8 | "main": "index.html", 9 | "scripts": { 10 | "start": "parcel src/public/index.html", 11 | "build": "NODE_ENV=production parcel build src/public/index.html --public-url ./ -d public/" 12 | }, 13 | "dependencies": { 14 | "bootstrap": "^4.3.1" 15 | }, 16 | "devDependencies": { 17 | "parcel-bundler": "^1.12.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/actions/addRequest.ts: -------------------------------------------------------------------------------- 1 | import { newRequestFields, newRequest } from "../types"; 2 | 3 | export const RESET = "RESET"; 4 | export const UPDATE_REQUEST_FIELDS = "UPDATE_REQUEST_FIELDS"; 5 | export const UPDATE_REQUEST_ROOT_FIELDS = "UPDATE_REQUEST_ROOT_FIELDS"; 6 | 7 | export const updateAddRequestFields = (payload: newRequestFields) => { 8 | return { 9 | type: UPDATE_REQUEST_FIELDS, 10 | payload 11 | }; 12 | }; 13 | 14 | export const resetAddRequest = () => { 15 | return { 16 | type: RESET 17 | }; 18 | }; 19 | 20 | export const updateRequestRootFields = (payload: newRequest) => { 21 | return { 22 | type: UPDATE_REQUEST_ROOT_FIELDS, 23 | payload 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /app/stylesheets/_grid.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | display: grid; 3 | } 4 | 5 | header .grid-container { 6 | grid-template-columns: auto 180px; 7 | } 8 | 9 | .form { 10 | grid-row-gap: 10px; 11 | grid-column-gap: 10px; 12 | grid-template-columns: auto 160px; 13 | padding: 5px 10px 15px 40px; 14 | background-color: var(--form-bg); 15 | } 16 | 17 | .response { 18 | grid-row: 1; 19 | grid-row-end: 2; 20 | } 21 | 22 | .content { 23 | grid-row: 1; 24 | grid-column: 2; 25 | } 26 | 27 | .full-url { 28 | grid-row: 3; 29 | grid-column: 1/3; 30 | } 31 | 32 | .urlText { 33 | word-break: break-all; 34 | } 35 | 36 | .response-action { 37 | grid-template-columns: 220px auto ; 38 | } 39 | -------------------------------------------------------------------------------- /app/stylesheets/_header.css: -------------------------------------------------------------------------------- 1 | header { 2 | padding: 10px 15px; 3 | background-color: var(--primary-color); 4 | } 5 | header .grid-container { 6 | z-index: 1; 7 | position: relative; 8 | } 9 | 10 | .logo { 11 | color: var(--primary-color); 12 | display: inline-block; 13 | font-size: 0; 14 | position: relative; 15 | text-decoration: none; 16 | vertical-align: middle; 17 | z-index: 1; 18 | } 19 | .logo svg { 20 | display: inline-block; 21 | height: 32px; 22 | margin-right: 10px; 23 | vertical-align: middle; 24 | width: 32px; 25 | } 26 | .logo span { 27 | color: #fff; 28 | display: inline-block; 29 | font-size: 20px; 30 | vertical-align: middle; 31 | } 32 | -------------------------------------------------------------------------------- /app/components/InterceptAllButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface InterceptAllButtonProps { 4 | handleCheckedRequests: React.MouseEventHandler; 5 | disabled: boolean; 6 | } 7 | 8 | export const InterceptAllButton: React.SFC = props => { 9 | return ( 10 | 19 | ); 20 | }; 21 | 22 | InterceptAllButton.displayName = "InterceptAllButton"; 23 | 24 | InterceptAllButton.defaultProps = { 25 | disabled: false 26 | }; 27 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Interceptor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/components/Intercept_Components/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { InterceptTextBox } from "./InterceptTextBox"; 3 | import { RequestObj } from "./../RequestList"; 4 | 5 | export const InterceptForm: React.SFC = props => ( 6 |
7 | 19 |
20 | ); 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /Privacy-policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | ## Effective date: September 06, 2018 4 | [Codemancers](https://codemancers.com) operates the Interceptor browser extension. ([Chrome](https://chrome.google.com/webstore/detail/interceptor/enenfaicdcfgcnjmiigcjbmlbaoapnen) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/xhr-interceptor/)). 5 | 6 | Interceptor browser extension does not collect or share personal information of any kind. Interceptor does not share any data accepted from the users with third parties. 7 | 8 | ## Changes to our Privacy Policy 9 | We may change this privacy policy from time to time. We will post changes in this policy, and suggest that you periodically revisit this policy to stay informed of any changes. 10 | 11 | ## Contact Us 12 | If you have any questions about this privacy policy, please contact us at [team@codemancers.com](mailto:team@codemancers.com) 13 | -------------------------------------------------------------------------------- /app/store/popup_store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, Middleware, combineReducers } from "redux"; 2 | import { createLogger } from "redux-logger"; 3 | import thunkMiddleware from "redux-thunk"; 4 | import aliases from "./aliases"; 5 | import { alias } from "webext-redux"; 6 | import { reducer as rootReducer } from "./../reducers/rootReducer"; 7 | import { addRequestReducer } from "../reducers/addRequest"; 8 | import { POPUP_PROPS } from "../types"; 9 | 10 | let enhancer: any; 11 | if (process.env.NODE_ENV !== "production") { 12 | const logger: Middleware = createLogger({ 13 | collapsed: true 14 | }); 15 | enhancer = applyMiddleware(alias(aliases), thunkMiddleware, logger); 16 | } else { 17 | enhancer = applyMiddleware(alias(aliases), thunkMiddleware); 18 | } 19 | export default function(initalState: POPUP_PROPS) { 20 | return createStore(combineReducers({ rootReducer, addRequestReducer }), initalState, enhancer); 21 | } 22 | -------------------------------------------------------------------------------- /app/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const Logo: React.SFC<{}> = () => ( 4 | 5 | 13 | 14 | 18 | 22 | 23 | 24 | 25 | INTERCEPTOR 26 | 27 | ); 28 | 29 | Logo.displayName = "Logo"; 30 | -------------------------------------------------------------------------------- /app/containers/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { render } from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { Store } from "webext-redux"; 5 | import { initialiseDefaults } from "./../actions"; 6 | 7 | import Popup from "./Popup"; 8 | 9 | const queryParams: chrome.tabs.QueryInfo = { 10 | active: true, 11 | currentWindow: true 12 | }; 13 | 14 | const store = new Store({ 15 | portName: "INTERCEPTOR" 16 | }); 17 | 18 | chrome.tabs.query(queryParams, tabs => { 19 | const tab = tabs[0]; 20 | if (!tab) return; 21 | 22 | const { id, url } = tab; 23 | if (typeof id === "undefined" || typeof url === "undefined") return; 24 | 25 | store.dispatch(initialiseDefaults(id, url, "")); 26 | 27 | store.ready().then(() => { 28 | render( 29 | 30 | 31 | , 32 | document.getElementById("root") as HTMLElement 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "module": "commonjs", 5 | "target": "es5", 6 | "noImplicitAny": true, 7 | "strictNullChecks": true, 8 | "jsx": "react", 9 | "outDir": "./dist", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "pretty": true, 16 | "removeComments": true, 17 | "strict": true, 18 | "sourceMap": true, 19 | "skipLibCheck": true, 20 | "lib": ["es2016", "es2015", "dom", "dom.iterable", "es6"], 21 | "typeRoots": ["node_modules/@types"] 22 | }, 23 | "jsx": true, 24 | "file_exclude_patterns": ["*.js, *.js.map", "*.json"], 25 | "include": ["app"], 26 | "exclude": [ 27 | "node_modules", 28 | "app/__mocks__", 29 | "app/__tests__", 30 | "app/images", 31 | "app/lib", 32 | "app/stylesheets" 33 | ], 34 | "types": ["jasmine", "chrome"] 35 | } 36 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Interceptor", 3 | "description": "Run web clients without backends by mocking HTTP requests", 4 | "version": "0.3.1", 5 | "manifest_version": 2, 6 | "icons": { 7 | "16": "images/icon-16.png", 8 | "48": "images/icon-48.png", 9 | "128": "images/icon-128.png" 10 | }, 11 | "background": { 12 | "scripts": [ 13 | "js/background.js" 14 | ] 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "http://*/*", 20 | "https://*/*" 21 | ], 22 | "js": [ 23 | "js/content.js" 24 | ], 25 | "run_at": "document_start" 26 | } 27 | ], 28 | "web_accessible_resources": [ 29 | "lib/nise.min.js" 30 | ], 31 | "browser_action": { 32 | "default_title": "Interceptor", 33 | "default_popup": "index.html", 34 | "default_icon": "images/icon.png" 35 | }, 36 | "permissions": [ 37 | "webRequest", 38 | "tabs", 39 | "http://*/*", 40 | "https://*/*" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /website/src/public/assets/svg/twitter-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/stylesheets/_variables.css: -------------------------------------------------------------------------------- 1 | /* 2 | Palette generated by Material Palette - materialpalette.com/teal/amber 3 | */ 4 | 5 | :root { 6 | --primary-color: #3A539B; 7 | --primary-color-dark: #1F3A93; 8 | --primary-color-light: #4B77BE; 9 | 10 | --primary-color-text: #FFFFFF; 11 | --primary-text-color: #212121; 12 | --secondary-text-color: #757575; 13 | 14 | --divider-color: #BDBDBD; 15 | 16 | --accent-color: #03A9F4; 17 | --accent-color-dark: #2196F3; 18 | 19 | --danger-color: #F44336; 20 | --danger-color-dark: #D32F2F; 21 | 22 | --disabled-color: #DFDFDF; 23 | --disabled-text-color: #9F9F9F; 24 | 25 | --form-bg: #f5f5f5; 26 | 27 | --sw: 100vw; /* --screen-width */ 28 | --bh: 32px; /* --button-height */ 29 | --bh-sm: 24px; /* --button-height-small */ 30 | --br: 3px; /* --border-radius */ 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2018 Codemancers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /app/components/Switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const Switch: React.SFC = props => { 4 | let classNames = ["switch", props.isOn ? "switch_is-on" : "switch_is-off"].join(" "); 5 | return ( 6 |
7 | Toggle Interception 8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | const ToggleButton: React.SFC = props => { 16 | let classNames = [ 17 | "toggle-button", 18 | props.isOn ? "toggle-button_position-right" : "toggle-button_position-left" 19 | ].join(" "); 20 | return
; 21 | }; 22 | 23 | interface ToggleButton { 24 | isOn: boolean; 25 | } 26 | 27 | interface Switch { 28 | isOn: boolean; 29 | handleSwitchToggle: (event: React.MouseEvent) => void; 30 | } 31 | 32 | Switch.displayName = "Switch"; 33 | 34 | Switch.defaultProps = { 35 | isOn: false 36 | }; 37 | 38 | ToggleButton.defaultProps = { 39 | isOn: false 40 | }; 41 | -------------------------------------------------------------------------------- /website/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 codemancers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/reducers/addRequest.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from "uuid"; 2 | 3 | import { newRequest, Action } from "../types"; 4 | import { RESET, UPDATE_REQUEST_FIELDS, UPDATE_REQUEST_ROOT_FIELDS } from "../actions/addRequest"; 5 | 6 | export const initialState: newRequest = { 7 | fields: { 8 | url: "", 9 | method: "GET", 10 | type: "xmlhttprequest", 11 | requestId: uuid().replace(/-/g, "") 12 | }, 13 | error: "" 14 | }; 15 | 16 | export const addRequestReducer = (state = initialState, action: Action) => { 17 | switch (action.type) { 18 | case UPDATE_REQUEST_FIELDS: 19 | return { 20 | ...state, 21 | fields: { 22 | ...state.fields, 23 | ...action.payload 24 | } 25 | }; 26 | case UPDATE_REQUEST_ROOT_FIELDS: 27 | return { 28 | ...state, 29 | ...action.payload 30 | }; 31 | case RESET: 32 | return { 33 | ...initialState, 34 | fields: { 35 | ...initialState.fields, 36 | requestId: uuid().replace(/-/g, "") 37 | } 38 | }; 39 | 40 | default: 41 | return state; 42 | } 43 | }; 44 | 45 | export default addRequestReducer; 46 | -------------------------------------------------------------------------------- /app/message_service.ts: -------------------------------------------------------------------------------- 1 | export type GenericCallback = (_: any) => void; 2 | 3 | // Outgoing 4 | export function enableLogging(tabId: number) { 5 | chrome.runtime.sendMessage({ message: "ENABLE_LOGGING", tabId }); 6 | } 7 | 8 | export function disableLogging(tabId: number) { 9 | chrome.runtime.sendMessage({ message: "DISABLE_LOGGING", tabId }); 10 | } 11 | 12 | export function clearData(tabId: number) { 13 | chrome.runtime.sendMessage({ message: "CLEAR_DATA", tabId }); 14 | chrome.tabs.sendMessage(tabId, { message: "CLEAR_DATA" }); 15 | } 16 | 17 | export function interceptChecked(tabId: number) { 18 | chrome.tabs.sendMessage(tabId, { 19 | message: "INTERCEPT_CHECKED", 20 | tabId 21 | }); 22 | } 23 | 24 | export function disableInterceptor(tabId: number) { 25 | chrome.tabs.sendMessage(tabId, { message: "DISABLE_INTERCEPTOR", tabId }); 26 | } 27 | 28 | export function updateBadgeIcon(tabId: number, disabledStatus: boolean) { 29 | chrome.runtime.sendMessage({ message: "UPDATE_BADGE_ICON", tabId, disabledStatus }); 30 | } 31 | 32 | export function updateBadgeText(tabId: number, count: number) { 33 | chrome.runtime.sendMessage({ message: "UPDATE_BADGE_TEXT", tabId, count }); 34 | } 35 | -------------------------------------------------------------------------------- /app/types/index.ts: -------------------------------------------------------------------------------- 1 | enum interceptStatus { 2 | Success = "Interception Success!", 3 | Fail = "Interception Disabled!" 4 | } 5 | 6 | export interface ReduxState { 7 | rootReducer: POPUP_PROPS; 8 | addRequestReducer: newRequest; 9 | } 10 | 11 | export interface POPUP_PROPS { 12 | currentTab: number; 13 | currentUrl: string; 14 | interceptStatus?: interceptStatus; 15 | tabRecord: any; 16 | showAddRuleModal: boolean; 17 | } 18 | 19 | export interface interceptOn { 20 | [tabId: number]: boolean; 21 | } 22 | 23 | export interface selectCheckBoxes { 24 | [index: number]: boolean; 25 | } 26 | 27 | export interface Action extends POPUP_PROPS { 28 | type: string; 29 | payload?: any; 30 | } 31 | 32 | export interface requestListProps extends POPUP_PROPS { 33 | requests: Array; 34 | handleCheckToggle: React.ChangeEventHandler; 35 | } 36 | 37 | export interface newRequestFields { 38 | url: string; 39 | method: string; 40 | requestId?: string; 41 | type?: string; 42 | tabId?: number; 43 | } 44 | 45 | export interface requestRootFields { 46 | fields?: newRequestFields; 47 | error?: string; 48 | } 49 | 50 | export interface newRequest { 51 | fields: newRequestFields; 52 | error: string; 53 | } 54 | -------------------------------------------------------------------------------- /app/__tests__/intercept_all.test.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import {shallow} from "enzyme" 3 | import { InterceptAllButton } from "./../components/InterceptAllButton"; 4 | 5 | const interceptAllButtonProps = { 6 | disabled: true; 7 | handleCheckedRequests: jest.fn(); 8 | }; 9 | 10 | describe(" initial state", () => { 11 | let InterceptAllButtonWrapper; 12 | beforeEach(() => { 13 | jest.clearAllMocks(); 14 | InterceptAllButtonWrapper = shallow(); 15 | }); 16 | 17 | test("InterceptAllButton renders without crashing", () => { 18 | expect(InterceptAllButtonWrapper).toBeDefined(); 19 | expect(InterceptAllButtonWrapper.props) 20 | .toBeDefined(); 21 | }); 22 | 23 | test("It contains one button element", ()=> { 24 | expect(InterceptAllButtonWrapper.find("button")).toHaveLength(1) 25 | }) 26 | }); 27 | 28 | describe(" onClick events", ()=> { 29 | let wrapper; 30 | beforeEach(()=>{ 31 | wrapper = shallow(); 32 | jest.clearAllMocks() 33 | }) 34 | 35 | test("onClick of button, should trigger props.handleCheckedRequests", ()=> { 36 | wrapper.find('button').simulate("click"); 37 | expect(interceptAllButtonProps.handleCheckedRequests).toBeCalled() 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /app/components/Intercept_Components/RequestHeaderList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface requestHeadersProps extends chrome.webRequest.WebRequestHeadersDetails { 4 | map: Function; 5 | } 6 | 7 | interface RequestHeadersProps { 8 | name: string; 9 | value: string; 10 | requestHeaders: requestHeadersProps; 11 | } 12 | 13 | export const RequestHeaderList: React.SFC = props => { 14 | return ( 15 |
16 | 17 |
    18 | {props.requestHeaders.map((requestHeader: RequestHeadersProps, index: number) => { 19 | if (requestHeader.value && requestHeader.name) { 20 | return ( 21 |
  • 22 | {requestHeader.name} :{" "} 23 | {requestHeader.name === "Cookie" ? ( 24 | {requestHeader.value} 25 | ) : ( 26 | {requestHeader.value} 27 | )} 28 |
  • 29 | ); 30 | } 31 | return null; 32 | })} 33 |
34 |
35 | ); 36 | }; 37 | 38 | RequestHeaderList.displayName = "RequestHeaderList"; 39 | 40 | RequestHeaderList.defaultProps = { 41 | name: "", 42 | value: "", 43 | requestHeaders: [] 44 | }; 45 | -------------------------------------------------------------------------------- /website/src/public/assets/svg/github-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/stylesheets/_reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing : border-box; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | ::-moz-selection { 8 | color: #fff; 9 | background: var(--primary-color-light); 10 | text-shadow: none 11 | } 12 | 13 | ::selection { 14 | color: #fff; 15 | background: var(--primary-color-light); 16 | text-shadow: none 17 | } 18 | 19 | html, 20 | body { 21 | font: inherit; 22 | height: 547px; 23 | position: relative; 24 | vertical-align: baseline; 25 | width: 500px; 26 | 27 | -webkit-text-size-adjust: 100%; 28 | -webkit-font-smoothing: antialiased 29 | } 30 | 31 | body { 32 | display: none; 33 | font-family: 'Barlow', sans-serif; 34 | font-size: 14px; 35 | line-height: 1.4; 36 | margin: 0 auto; 37 | opacity: 0; 38 | transition: opacity 200ms ease; 39 | } 40 | 41 | .container { 42 | margin: 0 15px; 43 | } 44 | 45 | section { 46 | padding: 15px 0; 47 | } 48 | 49 | a { 50 | text-decoration-skip: ink; 51 | } 52 | 53 | hr { 54 | display: block; 55 | height: 1px; 56 | border: 0; 57 | border-top: 1px solid var(--divider-color); 58 | margin: 1em 0; 59 | padding: 0 60 | } 61 | 62 | h1 { 63 | font-size: 3.247em; /* 51.957px */ 64 | } 65 | h2 { 66 | font-size: 2.566em; /* 41.053px */ 67 | } 68 | h3 { 69 | font-size: 2.027em; /* 32.437px */ 70 | } 71 | h4 { 72 | font-size: 1.602em; /* 25.629px */ 73 | } 74 | h5 { 75 | font-size: 1.266em; /* 20.25px */ 76 | } 77 | h6 { 78 | font-size: 1em; /* 16px */ 79 | } 80 | 81 | h1, h2, h3, h4, h5, h6 { 82 | margin-bottom: 10px; 83 | } 84 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 4 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 5 | 6 | let pathsToClean = { cleanOnceBeforeBuildPatterns: ["dist"] }; 7 | 8 | module.exports = { 9 | context: path.join(__dirname, "app"), 10 | entry: { 11 | popup: "./containers/app.tsx", 12 | background: "./background/background.ts", 13 | content: "./content/content.ts", 14 | utils: "./utils/utils.ts" 15 | }, 16 | output: { 17 | path: path.join(__dirname, "dist"), 18 | filename: "js/[name].js", 19 | crossOriginLoading: "anonymous" 20 | }, 21 | mode: "production", 22 | resolve: { 23 | extensions: [".ts", ".tsx", ".js", ".jsx"] 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.tsx?$/, 29 | use: [{ loader: "ts-loader", options: { transpileOnly: true } }], 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: [ 35 | { 36 | loader: "css-loader", 37 | options: { 38 | minimize: true 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new CleanWebpackPlugin(pathsToClean), 47 | new CopyWebpackPlugin([ 48 | { from: "manifest.json" }, 49 | { from: "index.html" }, 50 | { from: "./lib/*" }, 51 | { from: "stylesheets/*" }, 52 | { from: "images/*" } 53 | ]) 54 | ] 55 | }; 56 | -------------------------------------------------------------------------------- /app/__tests__/request_list.test.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { shallow, mount } from "enzyme"; 3 | import RequestList from "./../components/RequestList"; 4 | 5 | const commonProps = { 6 | tabRecord: { 7 | PageDetails: [], 8 | checkedReqs: {}, 9 | enabledStatus: true, 10 | errorMessage: "", 11 | interceptStatus: "", 12 | isInterceptorOn: true, 13 | requests: [] 14 | }, 15 | currentTabId: 2328, 16 | fetchResponse: jest.fn(), 17 | handleCheckToggle: jest.fn(), 18 | handleCheckedRequests: jest.fn(), 19 | handleContentTypeChange: jest.fn(), 20 | handlePaginationChange: jest.fn(), 21 | handleRespTextChange: jest.fn(), 22 | handleStatusCodeChange: jest.fn(), 23 | handleSwitchToggle: jest.fn(), 24 | updateInterceptorStatus: jest.fn() 25 | }; 26 | let wrapper; 27 | let RowComponent; 28 | 29 | describe("RequestList initial state", () => { 30 | beforeEach(() => { 31 | jest.clearAllMocks(); 32 | wrapper = shallow(); 33 | }); 34 | test("Request list must be empty", () => { 35 | expect(commonProps.tabRecord.requests).toHaveLength(0); 36 | }); 37 | 38 | test("Only one ReactTable component should be present", () => { 39 | wrapper = shallow(); 40 | expect(wrapper.find("ReactTable")).toHaveLength(1); 41 | }); 42 | }); 43 | 44 | describe("on Click Events", () => { 45 | const wrapper; 46 | beforeEach(() => { 47 | wrapper = shallow(); 48 | jest.clearAllMocks(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /app/stylesheets/_toggleSwitch.css: -------------------------------------------------------------------------------- 1 | .toggle-switch-container { 2 | font-size: 4px; 3 | } 4 | 5 | .toggle-switch-container span{ 6 | font-size: 4em; 7 | display: inline-block; 8 | vertical-align: middle; 9 | } 10 | 11 | .switch { 12 | position: relative; 13 | width: 8em; 14 | height: 5em; 15 | border-radius: 20%; 16 | transition: background-color 100ms ease-out; 17 | z-index: 1; 18 | display: inline-block; 19 | vertical-align: middle; 20 | margin: 0 10px; 21 | } 22 | .switch:before, .switch:after { 23 | content: ""; 24 | position: absolute; 25 | top: 0; 26 | background-color: inherit; 27 | border-radius: 50%; 28 | width: 5em; 29 | height: 5em; 30 | z-index: 2; 31 | } 32 | .switch:before { 33 | left: -1em; 34 | } 35 | .switch:after { 36 | right: -1em; 37 | } 38 | 39 | .toggle-button { 40 | position: absolute; 41 | width: 5em; 42 | height: 5em; 43 | background-color: #353535; 44 | border-radius: 50%; 45 | transition: -webkit-transform 100ms ease-in-out; 46 | transition: transform 100ms ease-in-out; 47 | transition: transform 100ms ease-in-out, -webkit-transform 100ms ease-in-out; 48 | z-index: 3; 49 | border: 0.05em solid #353535; 50 | top: -0.05em; 51 | } 52 | 53 | .switch_is-off { 54 | background-color: #FF495C; 55 | } 56 | 57 | .switch_is-on { 58 | background-color: #3DDC97; 59 | } 60 | 61 | .toggle-button_position-left { 62 | -webkit-transform: translateX(-1.05em); 63 | transform: translateX(-1.05em); 64 | } 65 | 66 | .toggle-button_position-right { 67 | -webkit-transform: translateX(4.05em); 68 | transform: translateX(4.05em); 69 | } -------------------------------------------------------------------------------- /website/src/public/assets/styles/faq.css: -------------------------------------------------------------------------------- 1 | .faq-section { 2 | margin: 40px auto; 3 | } 4 | .faq-item { 5 | padding-bottom: 10px; 6 | } 7 | .box { 8 | background: rgba(246, 246, 246, 1); 9 | color: #666666; 10 | padding-top: 15px; 11 | padding-bottom: 15px; 12 | padding-left: 20px; 13 | font-size: 13px; 14 | text-transform: none; 15 | cursor: pointer; 16 | border: 1px solid #d9d9d9; 17 | } 18 | .answer { 19 | display: none; 20 | background: #ffffff; 21 | word-wrap: break-word; 22 | padding: 20px; 23 | border-bottom: 1px solid #d9d9d9; 24 | border-left: 1px solid #d9d9d9; 25 | border-right: 1px solid #d9d9d9; 26 | color: #000000; 27 | padding-left: 30px; 28 | } 29 | .fa-plus { 30 | content: url('data:image/svg+xml,'); 31 | vertical-align: -0.2em; 32 | padding-left: 0.1em; 33 | } 34 | .fa-minus { 35 | content: url('data:image/svg+xml,'); 36 | vertical-align: -0.2em; 37 | padding-left: 0.1em; 38 | } 39 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 4 | const TsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 5 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 6 | 7 | let pathsToClean = { cleanOnceBeforeBuildPatterns: ["dist"] }; 8 | 9 | module.exports = { 10 | context: path.join(__dirname, "app"), 11 | entry: { 12 | popup: "./containers/app.tsx", 13 | background: "./background/background.ts", 14 | content: "./content/content.ts", 15 | utils: "./utils/utils.ts" 16 | }, 17 | output: { 18 | path: path.join(__dirname, "dist"), 19 | filename: "js/[name].js", 20 | crossOriginLoading: "anonymous" 21 | }, 22 | devtool: "source-map", 23 | mode: "development", 24 | resolve: { 25 | extensions: [".ts", ".tsx", ".js", ".jsx"] 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.tsx?$/, 31 | use: [{ loader: "ts-loader", options: { transpileOnly: true } }], 32 | exclude: /node_modules/ 33 | }, 34 | { 35 | test: /\.css$/, 36 | use: [ 37 | { 38 | loader: "css-loader", 39 | options: { 40 | minimize: true 41 | } 42 | } 43 | ], 44 | exclude: /node_modules/ 45 | } 46 | ] 47 | }, 48 | plugins: [ 49 | new CleanWebpackPlugin(pathsToClean), 50 | new CopyWebpackPlugin([ 51 | { from: "manifest.json" }, 52 | { from: "index.html" }, 53 | { from: "./lib/*" }, 54 | { from: "stylesheets/*" }, 55 | { from: "images/*" } 56 | ]), 57 | new TsCheckerWebpackPlugin({ 58 | tsconfig: path.resolve("tsconfig.json"), 59 | memoryLimit: 512, 60 | diagnosticFormatter: "ts-loader", 61 | watch: ["./app"] // optional but improves performance (fewer stat calls) 62 | }) 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /app/store/aliases.ts: -------------------------------------------------------------------------------- 1 | import * as axios from "axios"; 2 | import { fetchSuccess, fetchFailure,fetchingResponse, handleRespTextChange } from "./../actions"; 3 | 4 | interface payload { 5 | requestDetails: chrome.webRequest.WebRequestHeadersDetails; 6 | tabId: number; 7 | payload: any; 8 | } 9 | interface reqHeaderNameValuePair { 10 | name: string; 11 | value: string; 12 | } 13 | 14 | const fetchDataAlias = (payload: payload) => { 15 | return (dispatch: any) => { 16 | const { method, url, requestId, requestHeaders, tabId } = payload.payload.requestDetails; 17 | const requestHeadersObject = requestHeaders 18 | ? requestHeaders.reduce( 19 | (accumulatedObj = {}, reqHeaderNameValuePair: reqHeaderNameValuePair) => { 20 | accumulatedObj[reqHeaderNameValuePair.name] = reqHeaderNameValuePair.value; 21 | return accumulatedObj; 22 | }, 23 | {} 24 | ) 25 | : {}; 26 | 27 | dispatch(fetchingResponse(tabId,requestId,true)); 28 | 29 | axios({ 30 | method, 31 | url, 32 | requestHeadersObject 33 | }) 34 | .then(({ data, headers }: axios.AxiosResponse) => { 35 | const stringifiedData = headers["content-type"].includes("json") 36 | ? JSON.stringify(data, null, 2) 37 | : data; 38 | dispatch(fetchSuccess("", requestId, tabId)); 39 | dispatch(handleRespTextChange(stringifiedData, requestId, tabId)); 40 | dispatch(fetchSuccess(stringifiedData, requestId, tabId)); 41 | }) 42 | .catch(() => { 43 | dispatch( 44 | fetchFailure( 45 | "Couldn't connect to server. Check your connection and try again.", 46 | requestId, 47 | tabId 48 | ) 49 | ); 50 | }) 51 | .finally(()=>{ 52 | dispatch(fetchingResponse(tabId,requestId,false)); 53 | }); 54 | }; 55 | }; 56 | 57 | export default { 58 | FETCH_DATA: fetchDataAlias 59 | }; 60 | -------------------------------------------------------------------------------- /website/src/public/assets/styles/index.css: -------------------------------------------------------------------------------- 1 | @import "bootstrap/dist/css/bootstrap.css"; 2 | 3 | :root { 4 | --bc: #3a539b; /* bc = brabd-color */ 5 | } 6 | 7 | ::-moz-selection { 8 | background: var(--bc); 9 | color: #fff; 10 | text-shadow: none; 11 | } 12 | ::selection { 13 | background: var(--bc); 14 | color: #fff; 15 | text-shadow: none; 16 | } 17 | ::-moz-selection { 18 | background: var(--bc); 19 | color: #fff; 20 | text-shadow: none; 21 | } 22 | 23 | .container { 24 | max-width: 853px; 25 | } 26 | header .navbar { 27 | padding: 5px; 28 | min-height: 42px; 29 | position: relative; 30 | background-color: var(--bc); 31 | } 32 | header .navbar:after { 33 | top: 0; 34 | left: 0; 35 | content: ""; 36 | position: absolute; 37 | border-bottom: 42px solid transparent; 38 | border-left: 42px solid #fff; 39 | } 40 | header { 41 | box-shadow: 0 5px 0 0 var(--bc); 42 | background: linear-gradient(to right, #fff 0%, #fff 50%, var(--bc) 50%, var(--bc) 100%); 43 | } 44 | .navbar-brand { 45 | font-size: 0; 46 | } 47 | .navbar-brand span { 48 | color: #fff; 49 | font-size: 20px; 50 | vertical-align: middle; 51 | } 52 | .z-1 { 53 | z-index: 1; 54 | } 55 | .videoWrapper { 56 | clear: both; 57 | float: none; 58 | height: 0; 59 | padding-bottom: 56.25%; 60 | padding-top: 25px; 61 | position: relative; 62 | width: 100%; 63 | } 64 | .videoWrapper iframe { 65 | height: 100%; 66 | left: 0; 67 | position: absolute; 68 | top: 0; 69 | width: 100%; 70 | } 71 | .display-5 { 72 | font-size: 26px; 73 | } 74 | hr.small { 75 | width: 25%; 76 | margin: 0 auto; 77 | border-top-width: 2px; 78 | } 79 | .bg-brand { 80 | background-color: var(--bc); 81 | } 82 | #features img { 83 | -webkit-filter: drop-shadow(3px 3px 3px #666); 84 | filter: drop-shadow(3px 3px 3px #666); 85 | } 86 | 87 | @media (min-width: 768px) { 88 | .w-md-25 { 89 | width: 25%; 90 | display: inline-block; 91 | } 92 | .w-md-25.btn-block + .btn-block { 93 | margin-top: 0; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [0.3.1](https://github.com/code-mancers/interceptor/compare/0.3.0...0.3.1) (2018-08-22) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * **package.json:** remove jest from lint staged ([3b89dde](https://github.com/code-mancers/interceptor/commit/3b89dde)) 8 | * **popup:** clear active interception when clear recorded data ([0f233ac](https://github.com/code-mancers/interceptor/commit/0f233ac)), closes [#149](https://github.com/code-mancers/interceptor/issues/149) 9 | 10 | 11 | 12 | 13 | # [0.3.0](https://github.com/code-mancers/interceptor/compare/0.2.1...0.3.0) (2018-08-20) 14 | 15 | * Change cursor to look like text-selection instead of pointer ([#122](https://github.com/code-mancers/interceptor/pull/122)) (@amit-mb) 16 | * Add title attribute to all buttons ([#126](https://github.com/code-mancers/interceptor/pull/126)) (@amit-mb) 17 | * Dragging vertically pushes the content down ([#130](https://github.com/code-mancers/interceptor/pull/130)) (@amit-mb) 18 | * Add support for interception against page refreshes ([#134](https://github.com/code-mancers/interceptor/pull/134)) (@amit-mb) 19 | * Update match-sorter and react-table packages ([#139](https://github.com/code-mancers/interceptor/pull/139)) (@amit-mb) 20 | * Add url interception without listening ([#133](https://github.com/code-mancers/interceptor/pull/133)) (@amit-mb) 21 | * pretty print json on clicking fetch data button ([#137](https://github.com/code-mancers/interceptor/pull/137)) 22 | 23 | 24 | 25 | 26 | ## [0.2.1](https://github.com/code-mancers/interceptor/compare/0.2.0...0.2.1) (2018-05-15) 27 | 28 | * Fix fill the response in textarea ([#120](https://github.com/code-mancers/interceptor/pull/120) ) 29 | 30 | 31 | ## [0.2.0](https://github.com/code-mancers/interceptor/compare/0.1.0...0.2.0) (2018-05-15) 32 | 33 | * Fetch and fIll the textarea with real response ([#109](https://github.com/code-mancers/interceptor/pull/109) ) 34 | * Using minified version of nise fake server extracted from sinon. ([#116](https://github.com/code-mancers/interceptor/pull/116) ) 35 | * upgraded webpack ([#113](https://github.com/code-mancers/interceptor/pull/113) ) 36 | * Fixed url filter issue ([#117](https://github.com/code-mancers/interceptor/pull/117) ) 37 | * size reduction from 1.07MB to 305KB 38 | 39 | 40 | # [0.1.0](https://github.com/code-mancers/interceptor/compare/0.0.2...0.1.0) (2018-04-12) 41 | 42 | * Added disable Interceptor option 43 | 44 | 45 | ## 0.0.2 (2018-04-07) 46 | 47 | * Intital Release 48 | 49 | -------------------------------------------------------------------------------- /app/__tests__/AddRuleModal.test.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { shallow } from "enzyme"; 3 | import AddRuleModal from "../components/AddRuleModal"; 4 | 5 | const addRequest = jest.fn(); 6 | const resetAddRequest = jest.fn(); 7 | const updateRequestRootFields = jest.fn(); 8 | 9 | const createTestProps = props => ({ 10 | addRequestDetails:{ fields : {url : "http://www.codemancers.com", method: "GET"}, error: ""}, 11 | updateAddRequestFields: jest.fn(), 12 | handleClose: jest.fn(), 13 | addRequest, 14 | resetAddRequest, 15 | updateRequestRootFields, 16 | tabId: 4545, 17 | // allow to override common props 18 | ...props 19 | }); 20 | 21 | describe("", () => { 22 | const wrapper, props; 23 | beforeEach(() => { 24 | jest.clearAllMocks(); 25 | props = createTestProps(); 26 | wrapper = shallow(); 27 | }); 28 | 29 | describe("renders all components without error", () => { 30 | test("Contains a Modal HOC component", () => { 31 | expect(wrapper).toBeDefined(); 32 | expect(wrapper.find("Modal")).toHaveLength(1); 33 | }); 34 | 35 | test("Contains a input, select and two button element", () => { 36 | expect(wrapper.find("input")).toHaveLength(1); 37 | expect(wrapper.find("select")).toHaveLength(1); 38 | expect(wrapper.find("button")).toHaveLength(2); 39 | }); 40 | 41 | test('on mount should reset the fields', () =>{ 42 | wrapper = shallow(, { disableLifecycleMethods: false }); 43 | expect(resetAddRequest).toBeCalled(); 44 | }); 45 | }); 46 | 47 | describe("onChange and click events", () => { 48 | test("onChange url input", () => { 49 | wrapper 50 | .find("input") 51 | .first() 52 | .simulate("change", { target: { value: "a", name: "url" } }); 53 | expect(props.updateAddRequestFields).toHaveBeenCalledWith({"url": "a"}) 54 | }); 55 | 56 | test("onChange method input", ()=> { 57 | wrapper 58 | .find("select") 59 | .first() 60 | .simulate("change", { target: { value: "POST", , name: "method" } }); 61 | expect(props.updateAddRequestFields).toHaveBeenCalledWith({"method": "POST"}) 62 | }); 63 | 64 | test("on valid URL, clicking on add rule should call addRequest", ()=> { 65 | wrapper 66 | .find(".btn-add-rule") 67 | .first() 68 | .simulate("click"); 69 | expect(props.addRequest).toHaveBeenCalledTimes(1); 70 | }); 71 | 72 | test("onInvalid Url, clicking on add rule should update error message", ()=> { 73 | const addRequest = { 74 | ...props.addRequestDetails, 75 | fields: { 76 | ...props.addRequestDetails.fields, 77 | url: 'aaaa', 78 | } 79 | }; 80 | const wrapperWithInvalidUrl = shallow(); 81 | wrapperWithInvalidUrl 82 | .find(".btn-add-rule") 83 | .first() 84 | .simulate("click"); 85 | expect(props.addRequest).not.toBeCalled(); 86 | expect(props.updateRequestRootFields).toHaveBeenCalledWith({"error": "Please Enter a valid URL"}) 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /app/components/AddRuleModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Modal } from "./ModalWrapper"; 4 | 5 | import { newRequest, newRequestFields, requestRootFields } from "../types"; 6 | 7 | interface AddRuleModalProps { 8 | addRequestDetails: newRequest; 9 | addRequest: (fields: newRequestFields) => void; 10 | updateAddRequestFields: (fields: newRequestFields) => void; 11 | updateRequestRootFields: (request: requestRootFields) => void; 12 | handleClose: () => void; 13 | resetAddRequest: () => void; 14 | } 15 | export default class AddRuleModal extends React.Component { 16 | isUrl = (str: string) => { 17 | try { 18 | new URL(str); 19 | return true; 20 | } catch { 21 | return false; 22 | } 23 | }; 24 | 25 | componentDidMount() { 26 | //erase the previously set error message on each re-render 27 | //reset the url to empty string and request method to "GET" 28 | this.props.resetAddRequest(); 29 | } 30 | 31 | handleAddRuleClick = () => { 32 | const { 33 | addRequestDetails: { fields } 34 | } = this.props; 35 | const IsUrl: boolean = this.isUrl(fields.url); 36 | if (IsUrl) { 37 | this.props.addRequest(fields); 38 | this.props.handleClose(); 39 | } else { 40 | this.props.updateRequestRootFields({ 41 | error: "Please Enter a valid URL" 42 | }); 43 | } 44 | }; 45 | 46 | updateField = (e: React.ChangeEvent) => { 47 | const { value, name } = e.target; 48 | this.props.updateAddRequestFields({ [name]: value }); 49 | }; 50 | 51 | render() { 52 | const { fields, error } = this.props.addRequestDetails; 53 | return ( 54 | 55 | {Boolean(error) &&

{error}

} 56 |
57 |
58 | 59 | 67 |
68 |
69 | 70 | 82 |
83 |
84 |
85 | 88 | 91 |
92 |
93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/stylesheets/styles.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import "_fonts.css"; 4 | @import "_variables.css"; 5 | @import "_reset.css"; 6 | @import "_grid.css"; 7 | @import "_header.css"; 8 | @import "_form.css"; 9 | @import "_table.css"; 10 | @import "_toggleSwitch.css"; 11 | 12 | .popup-error { 13 | background: #ef5350; 14 | color: #ffffff; 15 | border-radius: 3px; 16 | padding: 5px 10px; 17 | margin: 10px 15px; 18 | } 19 | 20 | .popup-error-message { 21 | line-height: 1.6; 22 | font-size: 14px; 23 | } 24 | .popup-error-message:before{ 25 | content:url('./../images/exclamation-circle-solid.svg'); 26 | } 27 | 28 | .popup { 29 | width: 100%; 30 | } 31 | .url { 32 | white-space: nowrap; 33 | overflow: hidden; 34 | text-overflow: ellipsis; 35 | } 36 | .requestHeaderContainer { 37 | font-size: 12px; 38 | grid-row: 4; 39 | grid-column: 1 / 3; 40 | word-break: break-all; 41 | } 42 | .requestHeader { 43 | list-style: none; 44 | } 45 | .requestHeaderName { 46 | font-weight: bold; 47 | } 48 | .requestHeaderValue { 49 | word-wrap: break-all; 50 | } 51 | .cookie { 52 | overflow: hidden; 53 | text-overflow: ellipsis; 54 | white-space: nowrap; 55 | width: 390px; 56 | display: inline-block; 57 | } 58 | .responseTextlabel{ 59 | display:inline-block; 60 | } 61 | .fetch-responsetext{ 62 | float:right; 63 | cursor: pointer; 64 | border: none; 65 | border-radius: var(--br); 66 | margin: 0 0 5px 0; 67 | font-weight: bold; 68 | } 69 | .responseText{ 70 | min-width: 278px; 71 | max-width: 278px; 72 | } 73 | .select-status{ 74 | width: 155px; 75 | } 76 | .mt15 { 77 | margin-top: 15px; 78 | } 79 | .modal { 80 | z-index:100; 81 | position: fixed; 82 | background: rgba(0,0,0,0.7); 83 | width: 100%; 84 | height: 100%; 85 | top: 0; 86 | left: 0; 87 | padding: 20px; 88 | } 89 | .modal-content { 90 | background: #fff; 91 | -webkit-transform: translate(0,-50%); 92 | -ms-transform: translate(0,-50%); 93 | -o-transform: translate(0,-50%); 94 | transform: translate(0,-50%); 95 | -webkit-transition: -webkit-transform .3s ease-out; 96 | -o-transition: -o-transform .3s ease-out; 97 | transition: transform .3s ease-out; 98 | position: relative; 99 | top: 50%; 100 | } 101 | .modal-header { 102 | padding: 10px 20px 0; 103 | border-bottom: 1px solid #ccc; 104 | } 105 | .modal-body { 106 | padding: 20px; 107 | } 108 | .modal-footer { 109 | padding: 15px; 110 | background-color: #eee; 111 | } 112 | .modal label { 113 | display: inline-block; 114 | max-width: 100%; 115 | margin-bottom: 5px; 116 | font-weight: 700; 117 | } 118 | .modal-close { 119 | cursor: pointer; 120 | font-size: 40px; 121 | line-height: 30px; 122 | float: right; 123 | margin-top: -2px; 124 | } 125 | .modal-close:hover { 126 | color: #666; 127 | } 128 | .modal-close:active { 129 | color: #999; 130 | } 131 | .form-control { 132 | display: block; 133 | width: 100%; 134 | height: 34px; 135 | padding: 6px 12px; 136 | font-size: 14px; 137 | line-height: 1.42857143; 138 | color: #555; 139 | background-color: #fff; 140 | background-image: none; 141 | border: 1px solid #ccc; 142 | border-radius: 4px; 143 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); 144 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075); 145 | -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; 146 | -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; 147 | transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; 148 | } 149 | #success-msg{ 150 | text-align: center; 151 | padding: 5px; 152 | } 153 | -------------------------------------------------------------------------------- /app/__tests__/intercept_box.test.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { shallow } from "enzyme"; 3 | import { InterceptTextBox } from "./../components/Intercept_Components/InterceptTextBox"; 4 | 5 | const createTestProps = (props = {}) => ({ 6 | // common props 7 | currentTabId: 2347, 8 | requestRecords: { 9 | contentType: "", 10 | responseError: "", 11 | responseText: "", 12 | serverError: "", 13 | serverResponse: "", 14 | statusCode: "" 15 | }, 16 | fetchResponse: jest.fn(), 17 | handleContentTypeChange: jest.fn(), 18 | handleRespTextChange: jest.fn(), 19 | handleStatusCodeChange: jest.fn(), 20 | rowProps: { 21 | checkbox: { requestId: 123 } 22 | }, 23 | fetchFailure: jest.fn() 24 | ...props 25 | }); 26 | // allow to override common props 27 | 28 | describe("Input and select Field tests", () => { 29 | let wrapper, props; 30 | describe("Default State", () => { 31 | beforeEach(() => { 32 | jest.clearAllMocks(); 33 | props = createTestProps(); 34 | wrapper = shallow(); 35 | }); 36 | 37 | test("it renders without crashing", () => { 38 | expect(wrapper).toBeDefined(); 39 | expect(wrapper.props).toBeDefined(); 40 | }); 41 | test("it contains one input element", () => { 42 | expect(wrapper.find(".responseText")).toHaveLength(1); 43 | }); 44 | test("It containts a select element to change status", () => { 45 | expect(wrapper.find(".select-status")).toHaveLength(1); 46 | }); 47 | test("It contains a select element to change content-type", () => { 48 | expect(wrapper.find(".content-type-select")).toHaveLength(1); 49 | }); 50 | test("It contains one RequestHeaderList component", () => { 51 | expect(wrapper.find("RequestHeaderList")).toHaveLength(1); 52 | }); 53 | }); 54 | 55 | describe("Show default values for response, status and content-type", () => { 56 | beforeEach(() => { 57 | jest.clearAllMocks(); 58 | props = createTestProps(); 59 | wrapper = shallow(); 60 | }); 61 | 62 | test("default value for response text", () => { 63 | expect(wrapper.find(".responseText").props().defaultValue).toEqual(""); 64 | }); 65 | test("default value for status text", () => { 66 | expect(wrapper.find(".select-status").props().value).toEqual("200"); 67 | }); 68 | test("default value for status text", () => { 69 | expect(wrapper.find(".content-type-select").props().value).toEqual("application/json"); 70 | }); 71 | }); 72 | 73 | describe("onChange events", () => { 74 | beforeEach(() => { 75 | jest.clearAllMocks(); 76 | props = createTestProps(); 77 | wrapper = shallow(); 78 | }); 79 | test("handleRespTextChange should be called on response field change", () => { 80 | wrapper.find(".responseText").simulate("change", { target: { value: "h" } }); 81 | expect(props.handleRespTextChange).toHaveBeenCalledWith("h", 123, 2347); 82 | }); 83 | test("handleStatusCodeChange should be called on status field change", () => { 84 | wrapper.find(".select-status").simulate("change", { target: { value: "404" } }); 85 | expect(props.handleStatusCodeChange).toHaveBeenCalledWith("404", 123, 2347); 86 | }); 87 | test("handleContentTypeChange should be called on content-type field change", () => { 88 | wrapper.find(".content-type-select").simulate("change", { target: { value: "text/html" } }); 89 | expect(props.handleContentTypeChange).toHaveBeenCalledWith("text/html", 123, 2347); 90 | }); 91 | test("props.fetchResponse and should be called on Fetch Response button click and error message should be cleared", () => { 92 | wrapper.find(".fetch-responsetext").simulate("click"); 93 | expect(props.fetchFailure).toHaveBeenCalledWith("", 123 , 2347); 94 | expect(props.fetchResponse).toHaveBeenCalledWith({ requestId: 123 }, 2347); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /website/.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | ## AUTO-DETECT 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | ## SOURCE CODE 19 | *.bat text eol=crlf 20 | *.coffee text 21 | *.css text 22 | *.htm text 23 | *.html text 24 | *.inc text 25 | *.ini text 26 | *.js text 27 | *.json text 28 | *.jsx text 29 | *.less text 30 | *.od text 31 | *.onlydata text 32 | *.php text 33 | *.pl text 34 | *.py text 35 | *.rb text 36 | *.sass text 37 | *.scm text 38 | *.scss text 39 | *.sh text eol=lf 40 | *.sql text 41 | *.styl text 42 | *.tag text 43 | *.ts text 44 | *.tsx text 45 | *.xml text 46 | *.xhtml text 47 | 48 | ## DOCKER 49 | *.dockerignore text 50 | Dockerfile text 51 | 52 | ## DOCUMENTATION 53 | *.markdown text 54 | *.md text 55 | *.mdwn text 56 | *.mdown text 57 | *.mkd text 58 | *.mkdn text 59 | *.mdtxt text 60 | *.mdtext text 61 | *.txt text 62 | AUTHORS text 63 | CHANGELOG text 64 | CHANGES text 65 | CONTRIBUTING text 66 | COPYING text 67 | copyright text 68 | *COPYRIGHT* text 69 | INSTALL text 70 | license text 71 | LICENSE text 72 | NEWS text 73 | readme text 74 | *README* text 75 | TODO text 76 | 77 | ## TEMPLATES 78 | *.dot text 79 | *.ejs text 80 | *.haml text 81 | *.handlebars text 82 | *.hbs text 83 | *.hbt text 84 | *.jade text 85 | *.latte text 86 | *.mustache text 87 | *.njk text 88 | *.phtml text 89 | *.tmpl text 90 | *.tpl text 91 | *.twig text 92 | 93 | ## LINTERS 94 | .babelrc text 95 | .csslintrc text 96 | .eslintrc text 97 | .htmlhintrc text 98 | .jscsrc text 99 | .jshintrc text 100 | .jshintignore text 101 | .prettierrc text 102 | .stylelintrc text 103 | 104 | ## CONFIGS 105 | *.bowerrc text 106 | *.cnf text 107 | *.conf text 108 | *.config text 109 | .browserslistrc text 110 | .editorconfig text 111 | .gitattributes text 112 | .gitconfig text 113 | .gitignore text 114 | .htaccess text 115 | *.npmignore text 116 | *.yaml text 117 | *.yml text 118 | browserslist text 119 | Makefile text 120 | makefile text 121 | 122 | ## HEROKU 123 | Procfile text 124 | .slugignore text 125 | 126 | ## GRAPHICS 127 | *.ai binary 128 | *.bmp binary 129 | *.eps binary 130 | *.gif binary 131 | *.ico binary 132 | *.jng binary 133 | *.jp2 binary 134 | *.jpg binary 135 | *.jpeg binary 136 | *.jpx binary 137 | *.jxr binary 138 | *.pdf binary 139 | *.png binary 140 | *.psb binary 141 | *.psd binary 142 | *.svg text 143 | *.svgz binary 144 | *.tif binary 145 | *.tiff binary 146 | *.wbmp binary 147 | *.webp binary 148 | 149 | ## AUDIO 150 | *.kar binary 151 | *.m4a binary 152 | *.mid binary 153 | *.midi binary 154 | *.mp3 binary 155 | *.ogg binary 156 | *.ra binary 157 | 158 | ## VIDEO 159 | *.3gpp binary 160 | *.3gp binary 161 | *.as binary 162 | *.asf binary 163 | *.asx binary 164 | *.fla binary 165 | *.flv binary 166 | *.m4v binary 167 | *.mng binary 168 | *.mov binary 169 | *.mp4 binary 170 | *.mpeg binary 171 | *.mpg binary 172 | *.ogv binary 173 | *.swc binary 174 | *.swf binary 175 | *.webm binary 176 | 177 | ## ARCHIVES 178 | *.7z binary 179 | *.gz binary 180 | *.jar binary 181 | *.rar binary 182 | *.tar binary 183 | *.zip binary 184 | 185 | ## FONTS 186 | *.ttf binary 187 | *.eot binary 188 | *.otf binary 189 | *.woff binary 190 | *.woff2 binary 191 | 192 | ## EXECUTABLES 193 | *.exe binary 194 | *.pyc binary -------------------------------------------------------------------------------- /app/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { newRequestFields } from "../types"; 2 | 3 | //ACTION CONSTANTS 4 | export const ERROR = "ERROR"; 5 | export const CLEAR_REQUESTS = "CLEAR_REQUESTS"; 6 | export const TOGGLE_CHECKBOX = "TOGGLE_CHECKBOX"; 7 | export const STATUSCODE_CHANGE = "STATUSCODE_CHANGE"; 8 | export const RESP_TEXT_CHANGE = "RESP_TEXT_CHANGE"; 9 | export const CONTENT_TYPE_CHANGE = "CONTENT_TYPE_CHANGE"; 10 | export const PAGINATION_CHANGE = "PAGINATION_CHANGE"; 11 | export const UPDATE_MESSAGE = "UPDATE_MESSAGE"; 12 | export const UPDATE_INTERCEPTOR_STATUS = "UPDATE_INTERCEPTOR_STATUS"; 13 | export const FETCH_DATA = "FETCH_DATA"; 14 | export const FETCH_DATA_IN_PROGRESS = "FETCH_DATA_IN_PROGRESS"; 15 | export const FETCH_DATA_SUCCESS = "FETCH_DATA_SUCCESS"; 16 | export const FETCH_DATA_FAILURE = "FETCH_DATA_FAILURE"; 17 | export const UPDATE_REQUEST = "UPDATE_REQUEST"; 18 | export const TOGGLE_LISTENING = "TOGGLE_LISTENING"; 19 | export const INITIALISE_DEFAULTS = "INITIALISE_DEFAULTS"; 20 | export const TOGGLE_SHOW_ADD_REQUEST = "TOGGLE_SHOW_ADD_REQUEST"; 21 | export const CHANGE_URL_TABLE = "CHANGE_URL_TABLE"; 22 | 23 | // Action Creators 24 | export function errorNotify(errorMessage: string, tabId: number) { 25 | return { type: ERROR, payload: { errorMessage, tabId } }; 26 | } 27 | 28 | export function clearFields(tabId: number) { 29 | return { type: CLEAR_REQUESTS, payload: { tabId } }; 30 | } 31 | export function handleCheckToggle(tabId: number, reqId: number, checked: boolean) { 32 | return { type: TOGGLE_CHECKBOX, payload: { tabId, reqId, checked } }; 33 | } 34 | export function handleStatusCodeChange(value: string, requestId: string, tabId: number) { 35 | return { type: STATUSCODE_CHANGE, payload: { value, requestId, tabId } }; 36 | } 37 | export function handleRespTextChange(value: string, requestId: string, tabId: number) { 38 | return { type: RESP_TEXT_CHANGE, payload: { value, requestId, tabId } }; 39 | } 40 | export function handleContentTypeChange(value: string, requestId: string, tabId: number) { 41 | return { type: CONTENT_TYPE_CHANGE, payload: { value, requestId, tabId } }; 42 | } 43 | export function handlePaginationChange(value: string, tabId: number, field: string) { 44 | return { type: PAGINATION_CHANGE, payload: { field, value, tabId } }; 45 | } 46 | export function sendMessageToUI(message: string, tabId: number) { 47 | return { type: UPDATE_MESSAGE, payload: { message, tabId } }; 48 | } 49 | export function updateInterceptorStatus(tabId: number, value: boolean) { 50 | return { type: UPDATE_INTERCEPTOR_STATUS, payload: { tabId, value } }; 51 | } 52 | export function fetchingResponse( 53 | tabId: number, 54 | requestId:any, 55 | fetching: boolean 56 | ){ 57 | return { 58 | type: FETCH_DATA_IN_PROGRESS, 59 | payload:{ 60 | tabId, 61 | requestId, 62 | fetching 63 | } 64 | } 65 | } 66 | export function fetchResponse( 67 | requestDetails: chrome.webRequest.WebRequestHeadersDetails, 68 | tabId: number 69 | ) { 70 | return { 71 | type: FETCH_DATA, 72 | payload: { requestDetails, tabId } 73 | }; 74 | } 75 | export function fetchSuccess(data: string, requestId: string, tabId: number) { 76 | return { type: FETCH_DATA_SUCCESS, payload: { response: data, requestId, tabId } }; 77 | } 78 | export function fetchFailure(error: string, requestId: string, tabId: number) { 79 | return { type: FETCH_DATA_FAILURE, payload: { error: error, requestId, tabId } }; 80 | } 81 | export function updateRequest( 82 | tabId: number, 83 | request: Array 84 | ) { 85 | return { type: UPDATE_REQUEST, payload: { tabId, request } }; 86 | } 87 | export function toggleListeningRequests(tabId: number, enabledStatus: boolean) { 88 | return { type: TOGGLE_LISTENING, payload: { tabId, enabledStatus } }; 89 | } 90 | export function initialiseDefaults( 91 | currentTab: number, 92 | currentUrl: string, 93 | interceptStatus: string 94 | ) { 95 | return { 96 | type: INITIALISE_DEFAULTS, 97 | payload: { currentTab, currentUrl, interceptStatus } 98 | }; 99 | } 100 | 101 | export const toggleAddRequestForm = (showAddRuleModal: boolean) => { 102 | return { 103 | type: TOGGLE_SHOW_ADD_REQUEST, 104 | payload: { showAddRuleModal } 105 | }; 106 | }; 107 | export const handleChangeUrl = (value: string, tabId: number, index: number) => { 108 | return { type: CHANGE_URL_TABLE, payload: { value, tabId, index } }; 109 | }; 110 | -------------------------------------------------------------------------------- /app/background/background.ts: -------------------------------------------------------------------------------- 1 | import { wrapStore } from "webext-redux"; 2 | 3 | import { INITIAL_POPUP_STATE } from "./../reducers/rootReducer"; 4 | import { initialState } from "./../reducers/addRequest"; 5 | import createStore from "./../store/popup_store"; 6 | export const store = createStore({ 7 | rootReducer: INITIAL_POPUP_STATE, 8 | addRequestReducer: initialState 9 | }); 10 | import { toggleListeningRequests, updateRequest } from "./../actions"; 11 | 12 | interface TabRecord { 13 | tabId: number; 14 | enabled: boolean; 15 | requests: Array; 16 | } 17 | 18 | interface Recordings { 19 | [index: number]: TabRecord; 20 | } 21 | 22 | wrapStore(store, { 23 | portName: "INTERCEPTOR" 24 | }); 25 | 26 | class BackgroundWorker { 27 | currentTab: number = -1; 28 | data: Recordings; 29 | constructor() { 30 | this.data = {}; 31 | } 32 | 33 | startMessageListener = () => { 34 | //Send a message to content-script on when a page reloads 35 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 36 | if (changeInfo.status === "complete") { 37 | chrome.tabs.sendMessage(Number(tab.id), { message: "PAGE_REFRESHED", tabId }); 38 | } 39 | }); 40 | chrome.runtime.onMessage.addListener(request => { 41 | if (!request.tabId) { 42 | return; 43 | } 44 | this.currentTab = request.tabId; 45 | if (!this.data[this.currentTab]) { 46 | this.data[this.currentTab] = { 47 | enabled: false, 48 | requests: [], 49 | tabId: -1 50 | }; 51 | } 52 | switch (request.message) { 53 | case "ENABLE_LOGGING": { 54 | this.startTrackingRequests(request.tabId); 55 | break; 56 | } 57 | case "DISABLE_LOGGING": { 58 | this.stopTrackingRequests(request.tabId); 59 | break; 60 | } 61 | case "CLEAR_DATA": { 62 | this.clearData(request.tabId); 63 | break; 64 | } 65 | case "UPDATE_BADGE_ICON": { 66 | this.updateBadgeIcon(request.tabId, request.disabledStatus); 67 | break; 68 | } 69 | case "UPDATE_BADGE_TEXT": { 70 | this.updateBadgeText(request.tabId, request.count); 71 | break; 72 | } 73 | } 74 | }); 75 | }; 76 | 77 | updateBadgeIcon = (tabId: number, disabledStatus: boolean) => { 78 | const iconBadgeIconPath = disabledStatus ? "images/icon-disabled-16.png" : "images/icon-16.png"; 79 | chrome.browserAction.setIcon({ 80 | path: { 81 | "16": iconBadgeIconPath 82 | }, 83 | tabId 84 | }); 85 | }; 86 | 87 | updateBadgeText = (tabId: number, requestsLength: number) => { 88 | const text = `${requestsLength}`; 89 | chrome.browserAction.setBadgeText({ text, tabId }); 90 | }; 91 | 92 | callback = (details: any) => { 93 | const tabRecords = this.data[this.currentTab]; 94 | if (this.data[this.currentTab].enabled && this.currentTab === details.tabId) { 95 | this.updateBadgeText(this.currentTab, tabRecords.requests.length); 96 | if ( 97 | tabRecords.requests.filter( 98 | req => 99 | req.requestId === details.requestId || 100 | (req.url === details.url && req.method === details.method) 101 | ).length > 0 102 | ) { 103 | return; 104 | } 105 | tabRecords.requests.push(details); 106 | store.dispatch(updateRequest(details.tabId, details)); 107 | this.updateBadgeText(this.currentTab, tabRecords.requests.length); 108 | this.data[this.currentTab] = tabRecords; 109 | } 110 | }; 111 | 112 | startTrackingRequests = (tabId: number) => { 113 | this.data[this.currentTab].enabled = true; 114 | store.dispatch(toggleListeningRequests(tabId, true)); 115 | chrome.webRequest.onBeforeSendHeaders.addListener( 116 | this.callback, 117 | { 118 | urls: [""], 119 | types: ["xmlhttprequest"] 120 | }, 121 | ["requestHeaders"] 122 | ); 123 | }; 124 | 125 | stopTrackingRequests = (tabId: number) => { 126 | this.data[this.currentTab].enabled = false; 127 | store.dispatch(toggleListeningRequests(tabId, false)); 128 | }; 129 | 130 | clearData = (tabId: number) => { 131 | this.data[this.currentTab].requests = []; 132 | this.updateBadgeText(tabId, 0); 133 | }; 134 | } 135 | 136 | new BackgroundWorker().startMessageListener(); 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interceptor", 3 | "version": "0.3.1", 4 | "description": "A browser extension that mocks AJAX request at the browser level so you can run frontend without really starting a backend server.", 5 | "license": "MIT", 6 | "homepage": "https://github.com/code-mancers/interceptor#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/code-mancers/interceptor.git" 10 | }, 11 | "keywords": [ 12 | "interceptor", 13 | "mock-ajax-frontend", 14 | "mock-xhr-frontend", 15 | "chrome", 16 | "firefox-extension" 17 | ], 18 | "private": true, 19 | "devDependencies": { 20 | "@commitlint/cli": "^8.2.0", 21 | "@commitlint/config-conventional": "^8.2.0", 22 | "@commitlint/travis-cli": "^8.2.0", 23 | "@types/chrome": "^0.0.88", 24 | "@types/classnames": "^2.2.3", 25 | "@types/clean-webpack-plugin": "^0.1.2", 26 | "@types/enzyme": "^3.1.10", 27 | "@types/enzyme-adapter-react-16": "^1.0.2", 28 | "@types/fork-ts-checker-webpack-plugin": "^0.4.0", 29 | "@types/jest": "^24.0.18", 30 | "@types/react": "^16.3.14", 31 | "@types/react-dom": "^16.0.5", 32 | "@types/react-redux": "^7.1.2", 33 | "@types/react-table": "^6.7.11", 34 | "@types/redux-logger": "^3.0.6", 35 | "clean-webpack-plugin": "^3.0.0", 36 | "copy-webpack-plugin": "^5.0.4", 37 | "css-loader": "^3.2.0", 38 | "cz-conventional-changelog": "^3.0.2", 39 | "enzyme": "^3.3.0", 40 | "enzyme-adapter-react-16": "^1.1.1", 41 | "enzyme-adapter-react-helper": "^1.2.3", 42 | "fork-ts-checker-webpack-plugin": "^1.5.0", 43 | "gulp": "^4.0.2", 44 | "gulp-conventional-changelog": "^2.0.1", 45 | "gulp-git": "^2.8.0", 46 | "gulp-replace": "^1.0.0", 47 | "gulp-zip": "^5.0.0", 48 | "husky": "^3.0.5", 49 | "jest": "^24.9.0", 50 | "lint-staged": "^9.2.5", 51 | "react-hot-loader": "^4.12.13", 52 | "redux-logger": "^3.0.6", 53 | "run-sequence": "^2.2.1", 54 | "sign-addon": "^0.3.1", 55 | "source-map-loader": "^0.2.1", 56 | "ts-jest": "^24.1.0", 57 | "ts-loader": "^6.1.0", 58 | "tslint": "^5.20.0", 59 | "tslint-react": "^4.1.0", 60 | "typescript": "^3.6.3", 61 | "webpack": "^4.40.2", 62 | "webpack-cli": "^3.3.9" 63 | }, 64 | "engines": { 65 | "node": ">=10.15.1", 66 | "npm": ">=6.11.2" 67 | }, 68 | "jest": { 69 | "verbose": true, 70 | "moduleFileExtensions": [ 71 | "ts", 72 | "tsx", 73 | "js", 74 | "jsx" 75 | ], 76 | "globals": { 77 | "__DEV__": true, 78 | "__RCTProfileIsProfiling": false, 79 | "ts-jest": { 80 | "diagnostics": false 81 | } 82 | }, 83 | "transform": { 84 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 85 | }, 86 | "setupFilesAfterEnv": [ 87 | "/app/__tests__/setup.ts" 88 | ], 89 | "testRegex": "/__tests__/.*\\.test\\.(jsx|js)$", 90 | "testPathIgnorePatterns": [ 91 | "/node_modules/" 92 | ], 93 | "testURL": "http://localhost/" 94 | }, 95 | "scripts": { 96 | "build": "webpack -p --config webpack.config.prod.js", 97 | "watch": "webpack --config webpack.config.dev.js --watch --progress --colors", 98 | "test": "jest", 99 | "test:watch": "jest --watch", 100 | "commit": "git-cz", 101 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -u -s", 102 | "fx": "web-ext run -s dist", 103 | "release": "gulp release" 104 | }, 105 | "lint-staged": { 106 | "./app/*.{js,jsx,ts,tsx}": [ 107 | "prettier --config .prettierrc --write", 108 | "git add" 109 | ] 110 | }, 111 | "dependencies": { 112 | "axios": "^0.21.1", 113 | "classnames": "^2.2.5", 114 | "match-sorter": "^4.0.1", 115 | "react": "^16.9.0", 116 | "react-dom": "^16.9.0", 117 | "react-redux": "^7.1.1", 118 | "react-simple-contenteditable": "^0.0.5", 119 | "react-table": "^6.8.6", 120 | "redux": "^4.0.4", 121 | "redux-thunk": "^2.2.0", 122 | "uuid": "^3.2.1", 123 | "webext-redux": "^2.1.2" 124 | }, 125 | "config": { 126 | "commitizen": { 127 | "path": "./node_modules/cz-conventional-changelog" 128 | } 129 | }, 130 | "commitlint": { 131 | "extends": [ 132 | "@commitlint/config-conventional" 133 | ] 134 | }, 135 | "husky": { 136 | "hooks": { 137 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 138 | "pre-commit": "lint-staged" 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/components/RequestList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ReactTable from "react-table"; 3 | import matchSorter from "match-sorter"; 4 | import { InterceptForm } from "./../components/Intercept_Components"; 5 | 6 | export type onClickCallback = (e: React.MouseEvent) => void; 7 | export interface RequestObj { 8 | tabRecord: any; 9 | requestRecords: any; 10 | currentTabId: number; 11 | handleChangeUrl: (value: string, tabId: number, index: number) => void; 12 | handleCheckedRequests?: (requests: Array) => void; 13 | handleRespTextChange?: () => (value: string, reqId: string) => void; 14 | handleStatusCodeChange?: (value: string, reqId: string) => void; 15 | handleCheckToggle?: (tabId: number, reqId: number, checked: boolean) => void; 16 | handleContentTypeChange?: (value: string, reqId: string) => void; 17 | handlePaginationChange?: (rowSize: number, tabId: number, field: string) => void; 18 | updateInterceptorStatus?: (tabId: number) => void; 19 | fetchResponse: React.MouseEvent; 20 | handleSwitchToggle: any; 21 | fetchFailure: any; 22 | } 23 | const RequestList: React.SFC = props => { 24 | const columns = [ 25 | { 26 | Header: "Request URL", 27 | accessor: "url", 28 | Cell: ({ original }: any) => ( 29 |
30 | {original.url} 31 |
32 | ), 33 | filterable: true, 34 | filterMethod: (filter: any, rows: any) => { 35 | return matchSorter(rows, filter.value, { 36 | keys: ["url"], 37 | threshold: matchSorter.rankings.CONTAINS 38 | }); 39 | }, 40 | filterAll: true 41 | }, 42 | { 43 | Header: "Method", 44 | className: "method", 45 | accessor: "method", 46 | width: 100, 47 | filterable: true, 48 | filterMethod: (filter: any, row: any) => row[filter.id] === filter.value, 49 | Filter: ({ filter, onChange }: any) => ( 50 | 60 | ) 61 | }, 62 | { 63 | id: "checkbox", 64 | accessor: "", 65 | Cell: ({ original }: any) => { 66 | return ( 67 | { 72 | props.handleCheckToggle(props.currentTabId, original.requestId, e.target.checked); 73 | }} 74 | /> 75 | ); 76 | }, 77 | Header: "Intercept", 78 | sortable: false, 79 | width: 75, 80 | className: "text-center" 81 | } 82 | ]; 83 | 84 | return ( 85 |
86 | 96 | props.handlePaginationChange(changedPageNo, props.currentTabId, "currentPageNumber") 97 | } 98 | onPageSizeChange={changedRowSize => 99 | props.handlePaginationChange(changedRowSize, props.currentTabId, "currentRowSize") 100 | } 101 | collapseOnDataChange={false} 102 | SubComponent={row => { 103 | return ( 104 | 116 | ); 117 | }} 118 | /> 119 |
120 | ); 121 | }; 122 | export default RequestList; 123 | 124 | RequestList.defaultProps = {}; 125 | -------------------------------------------------------------------------------- /app/stylesheets/_form.css: -------------------------------------------------------------------------------- 1 | /* Form Resets */ 2 | button, select, input { 3 | vertical-align: baseline 4 | } 5 | button, select, textarea, input { 6 | font-family: 'Barlow', sans-serif; 7 | font-size: 100% 8 | } 9 | input:-webkit-autofill { 10 | text-shadow: none 11 | } 12 | [tabindex='-1']:focus { 13 | outline: none 14 | } 15 | input[type="search"] { 16 | -webkit-appearance: textfield; 17 | box-sizing: content-box; 18 | -webkit-box-sizing: content-box 19 | } 20 | input[type="search"]::-webkit-search-decoration { 21 | -webkit-appearance: none 22 | } 23 | input[type="search"]::-webkit-search-cancel-button { 24 | -webkit-appearance: none 25 | } 26 | 27 | .btn { 28 | min-height: var(--bh); 29 | min-width: var(--bh); 30 | line-height: var(--bh); 31 | border: none; 32 | border-radius: var(--br); 33 | display: inline-block; 34 | font-size: 1rem; 35 | outline: 0; 36 | padding: 0 1rem; 37 | font-weight: 600; 38 | cursor: pointer; 39 | margin-left: 10px; 40 | text-transform: uppercase; 41 | vertical-align: middle; 42 | background-color: #eee; 43 | -webkit-tap-highlight-color: transparent; 44 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); 45 | } 46 | .btn:focus, 47 | .btn:hover, 48 | .btn:active { 49 | background: var(--disabled-color); 50 | } 51 | .btn:active { 52 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 53 | } 54 | 55 | .btn-primary { 56 | background: var(--primary-color); 57 | color: var(--primary-color-text); 58 | } 59 | .btn-primary:focus, 60 | .btn-primary:hover, 61 | .btn-primary:active { 62 | background: var(--primary-color-dark); 63 | } 64 | 65 | .btn-secondary { 66 | background: var(--accent-color-dark); 67 | color: var(--primary-color-text); 68 | } 69 | .btn-secondary:focus, 70 | .btn-secondary:hover, 71 | .btn-secondary:active { 72 | background: var(--accent-color); 73 | } 74 | 75 | .btn-danger { 76 | background: var(--danger-color-dark); 77 | color: var(--primary-color-text); 78 | } 79 | .btn-danger:focus, 80 | .btn-danger:hover, 81 | .btn-danger:active { 82 | background: var(--danger-color); 83 | } 84 | 85 | .btn-md { 86 | padding: 0 .75rem; 87 | font-size: 12px; 88 | margin-left: 5px; 89 | } 90 | 91 | .btn-sm { 92 | min-width: var(--bh-sm); 93 | min-height: var(--bh-sm); 94 | line-height: var(--bh-sm); 95 | padding: 0 .75rem; 96 | font-size: 12px; 97 | margin-left: 5px; 98 | } 99 | 100 | .btn-block { 101 | width: 100%; 102 | margin: 0; 103 | } 104 | 105 | .btn.disabled, 106 | .btn:disabled, 107 | .btn[disabled] { 108 | pointer-events: none; 109 | background-color: var(--disabled-color); 110 | box-shadow: none; 111 | color: var(--disabled-text-color); 112 | cursor: default; 113 | } 114 | 115 | .btn.disabled:hover, 116 | .btn:disabled:hover, 117 | .btn[disabled]:hover { 118 | background-color: var(--disabled-color); 119 | color: var(--disabled-text-color); 120 | } 121 | 122 | /* .btn:hover, .btn:focus { 123 | filter: brightness(85%); 124 | } */ 125 | 126 | .btn-group .btn:first-child:not(:last-child) { 127 | border-top-right-radius: 0; 128 | border-bottom-right-radius: 0; 129 | } 130 | .btn-group .btn:last-child:not(:first-child) { 131 | border-top-left-radius: 0; 132 | border-bottom-left-radius: 0; 133 | } 134 | .btn-group .btn+.btn { 135 | margin-left: -1px; 136 | } 137 | 138 | input[type="text"], 139 | input[type="search"], textarea { 140 | display: block; 141 | width: 100%; 142 | padding: 0 5px; 143 | color: var(--primary-text-color); 144 | background-color: var(--primary-color-text); 145 | background-image: none; 146 | border: 1px solid var(--divider-color); 147 | border-radius: var(--br); 148 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075); 149 | transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; 150 | } 151 | 152 | textarea, 153 | .input-lg[type="text"] { 154 | min-height: calc(var(--bh) - 2px); 155 | line-height: calc(var(--bh) - 2px); 156 | } 157 | 158 | textarea { 159 | min-height: 85px; 160 | } 161 | 162 | fieldset { 163 | border: none 164 | } 165 | 166 | .control-group { 167 | margin-bottom: 15px; 168 | } 169 | 170 | label { 171 | font-weight: bold; 172 | margin-bottom: 5px; 173 | display: block; 174 | } 175 | 176 | .text-right { 177 | text-align: right; 178 | } 179 | 180 | .text-center { 181 | text-align: center; 182 | } 183 | 184 | .response-action { 185 | margin: 10px 15px; 186 | } 187 | .responseText{ 188 | font-family: 'Anonymous Pro',"Courier New", Courier, monospace; 189 | line-height: 1.2; 190 | display: inline-block; 191 | font-size: 0.8rem; 192 | vertical-align: middle; 193 | -webkit-tap-highlight-color: transparent; 194 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); 195 | padding: 5px; 196 | } 197 | 198 | .mt15 { 199 | margin-top: 15px; 200 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const { src, dest, watch, parallel, series, task } = require("gulp"); 2 | const zip = require("gulp-zip"); 3 | const replace = require("gulp-replace"); 4 | const sequence = require("run-sequence"); 5 | const git = require("gulp-git"); 6 | const conventionalChangelog = require("gulp-conventional-changelog"); 7 | const fs = require("fs"); 8 | const signAddon = require("sign-addon").default; 9 | 10 | const APP_PATH = "./app/"; 11 | const DIST_PATH = "./dist/"; 12 | const OUTPUT_PATH = "./web-ext-artifacts/"; 13 | 14 | function changelog() { 15 | return src("CHANGELOG.md", { 16 | buffer: false 17 | }) 18 | .pipe( 19 | conventionalChangelog({ 20 | preset: "angular" // Or to any other commit message convention you use. 21 | }) 22 | ) 23 | .pipe(dest("./")); 24 | } 25 | 26 | function commitChanges() { 27 | return ( 28 | src(["./package.json", "./CHANGELOG.md", "./app/manifest.json"]) 29 | .pipe(git.add()) 30 | // to avoid issue https://github.com/stevelacy/gulp-git/issues/186 31 | .pipe( 32 | git.commit(undefined, { 33 | args: '-m "build: release ' + process.env.NEW_VERSION + '"', 34 | disableMessageRequirement: true, 35 | disableAppendPaths: true 36 | }) 37 | ) 38 | .on("error", function(error) { 39 | console.log(error); 40 | }) 41 | ); 42 | } 43 | 44 | function pushChanges(done) { 45 | git.push('origin', 'master', done); 46 | return done(); 47 | } 48 | 49 | function createNewTag(done) { 50 | var version = getPackageJsonVersion(); 51 | git.tag(version, version, function(error) { 52 | if (error) { 53 | return done(error); 54 | } 55 | git.push('origin', 'master', {args: '--tags'}, done); 56 | return done(); 57 | }); 58 | 59 | function getPackageJsonVersion() { 60 | // We parse the json file instead of using require because require caches 61 | // multiple calls so the version number won't be updated 62 | return JSON.parse(fs.readFileSync("./package.json", "utf8")).version; 63 | } 64 | } 65 | 66 | function manifestVersion() { 67 | return src([DIST_PATH + "manifest.json"]) 68 | .pipe(replace(/(.*"version": ")(.*)(",.*)/g, "$1" + process.env.NEW_VERSION + "$3")) 69 | .pipe(dest(DIST_PATH)); 70 | } 71 | 72 | function manifestAppVersion() { 73 | return src([APP_PATH + "manifest.json"]) 74 | .pipe(replace(/(.*"version": ")(.*)(",.*)/g, "$1" + process.env.NEW_VERSION + "$3")) 75 | .pipe(dest(APP_PATH)); 76 | } 77 | 78 | function packageVersion() { 79 | return src(["./package.json"]) 80 | .pipe(replace(/(.*"version": ")(.*)(",.*)/g, "$1" + process.env.NEW_VERSION + "$3")) 81 | .pipe(dest("./")); 82 | } 83 | 84 | function xpi() { 85 | const manifest = require(DIST_PATH + "manifest.json"); 86 | const distFileName = manifest.name + "-" + manifest.version + ".xpi"; 87 | return src([DIST_PATH + "/**"]) 88 | .pipe(zip(distFileName)) 89 | .pipe(dest(OUTPUT_PATH)); 90 | } 91 | 92 | function zipDist() { 93 | const manifest = require(DIST_PATH + "manifest.json"); 94 | const distFileName = manifest.name + "-" + manifest.version + ".zip"; 95 | return src([DIST_PATH + "/**"]) 96 | .pipe(zip(distFileName)) 97 | .pipe(dest(OUTPUT_PATH)); 98 | } 99 | 100 | function zipApp() { 101 | const manifest = require(DIST_PATH + "manifest.json"); 102 | const distFileName = manifest.name + "-app-" + manifest.version + ".zip"; 103 | return src([APP_PATH + "/**"]) 104 | .pipe(zip(distFileName)) 105 | .pipe(dest(OUTPUT_PATH)); 106 | } 107 | 108 | function sign() { 109 | const manifest = require(DIST_PATH + "manifest.json"); 110 | const distFileName = manifest.name + "-" + manifest.version + ".xpi"; 111 | signAddon({ 112 | xpiPath: OUTPUT_PATH + distFileName, 113 | version: manifest.version, 114 | apiKey: process.env.AMO_API_KEY, 115 | apiSecret: process.env.AMO_API_SECRET, 116 | downloadDir: OUTPUT_PATH, 117 | channel: "listed", 118 | id: "{024f65fd-47e3-4556-bd93-4c0a1d08cd33}" 119 | }) 120 | .then(function(result) { 121 | if (result.success) { 122 | console.log("The following signed files were downloaded:"); 123 | console.log(result.downloadedFiles); 124 | console.log("Your extension ID is:"); 125 | console.log(result.id); 126 | } else { 127 | console.error("Your add-on could not be signed!"); 128 | console.error("Check the console for details."); 129 | } 130 | console.log(result.success ? "SUCCESS" : "FAIL"); 131 | }) 132 | .catch(function(error) { 133 | console.error("Signing error:", error); 134 | }); 135 | } 136 | 137 | exports.changelog = changelog; 138 | exports.release = series( 139 | parallel(manifestVersion, manifestAppVersion, packageVersion), 140 | changelog, 141 | commitChanges, 142 | pushChanges, 143 | createNewTag, 144 | parallel(xpi, zipDist, zipApp), 145 | sign 146 | ); 147 | -------------------------------------------------------------------------------- /app/__tests__/popup.test.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { shallow, mount } from "enzyme"; 3 | import { Popup } from "./../containers/Popup"; 4 | 5 | import * as MessageService from "../message_service"; 6 | 7 | jest.mock("../message_service"); 8 | const createTestProps = props => ({ 9 | tabRecord: { 10 | PageDetails: [], 11 | checkedReqs: {}, 12 | enabledStatus: false, 13 | errorMessage: "", 14 | interceptStatus: "", 15 | isInterceptorOn: true, 16 | requests: [] 17 | }, 18 | showAddRuleModal: true, 19 | addRequestDetails: { fields: { url: "codemancers.com", method: "GET", error: "" } }, 20 | currentTab: 1, 21 | currentUrl: "http://www.google.com", 22 | clearFields: jest.fn(), 23 | handleCheckToggle: jest.fn(), 24 | handleCheckedRequests: jest.fn(), 25 | handleContentTypeChange: jest.fn(), 26 | errorNotify: jest.fn(), 27 | handlePaginationChange: jest.fn(), 28 | handleRespTextChange: jest.fn(), 29 | handleStatusCodeChange: jest.fn(), 30 | toggleListeningRequests: jest.fn(), 31 | updateInterceptorStatus: jest.fn(), 32 | clearFields: jest.fn(), 33 | toggleAddRequestForm: jest.fn(), 34 | updateAddRequestFields: jest.fn(), 35 | resetAddRequest: jest.fn(), 36 | // allow to override common props 37 | ...props 38 | }); 39 | 40 | describe("Popup", () => { 41 | let wrapper, props; 42 | 43 | describe("default state", () => { 44 | beforeEach(() => { 45 | jest.clearAllMocks(); 46 | props = createTestProps(); 47 | wrapper = shallow(); 48 | }); 49 | 50 | test("it renders without crashing", () => { 51 | expect(wrapper).toBeDefined(); 52 | }); 53 | 54 | test("Contains one Logo component", () => { 55 | expect(wrapper.find("Logo")).toHaveLength(1); 56 | }); 57 | 58 | test("Contains one button elements", () => { 59 | expect(wrapper.find(".btn-secondary")).toHaveLength(1); 60 | }); 61 | 62 | test("Should contain one Switch component", () => { 63 | expect(wrapper.find("Switch")).toHaveLength(1); 64 | }); 65 | 66 | test("One InterceptButton component should be present", () => { 67 | expect(wrapper.find("InterceptAllButton")).toHaveLength(1); 68 | }); 69 | 70 | test("Should contain one Clear button", () => { 71 | expect(wrapper.find(".btn-clear")).toHaveLength(1); 72 | }); 73 | 74 | test("Contains one RequestList component", () => { 75 | expect(wrapper.find("RequestList")).toHaveLength(1); 76 | }); 77 | 78 | test("On start button click, should pass message 'EnableLogging' with tabId", () => { 79 | wrapper 80 | .find(".button-start-listening") 81 | .first() 82 | .simulate("click"); 83 | expect(MessageService.enableLogging).toHaveBeenCalledWith(1); 84 | }); 85 | test("contains a AddRuleModal HOC component", () => { 86 | expect(wrapper.find("AddRuleModal")).toHaveLength(1); 87 | }); 88 | }); 89 | 90 | describe("On enabled", () => { 91 | beforeEach(() => { 92 | jest.clearAllMocks(); 93 | }); 94 | 95 | test("on Stop button click, should pass message 'disableLogging' with tabId", () => { 96 | let localProps = createTestProps({ tabRecord: { enabledStatus: true } }); 97 | wrapper = shallow(); 98 | wrapper 99 | .find(".button-stop-listening") 100 | .first() 101 | .simulate("click"); 102 | expect(MessageService.disableLogging).toHaveBeenCalledWith(1); 103 | }); 104 | }); 105 | 106 | describe("On error", () => { 107 | test("should render error message", () => { 108 | jest.clearAllMocks(); 109 | let localProps = createTestProps({ tabRecord: { errorMessage: "Error" } }); 110 | wrapper = shallow(); 111 | expect(wrapper.find(".popup-error-message").text()).toEqual(expect.stringMatching("Error")); 112 | }); 113 | }); 114 | 115 | describe("On invalid url", () => { 116 | beforeEach(() => { 117 | jest.clearAllMocks(); 118 | }); 119 | test("should call errorNotify and disable interception", () => { 120 | let localProps = createTestProps({ 121 | currentUrl: "chrome://version" 122 | }); 123 | wrapper = shallow(); 124 | wrapper 125 | .find(".button-start-listening") 126 | .first() 127 | .simulate("click"); 128 | expect(localProps.errorNotify).toHaveBeenCalledWith( 129 | "Cannot Start Listening on chrome://version", 130 | 1 131 | ); 132 | }); 133 | }); 134 | 135 | describe("Intercept Success Message", () => { 136 | beforeEach(() => { 137 | jest.clearAllMocks(); 138 | }); 139 | test("Should display Success message on successfull intercept", () => { 140 | let localProps = createTestProps({ 141 | interceptStatus: "Interception Success!" 142 | }); 143 | wrapper = shallow(); 144 | expect(wrapper.find("#success-msg").text()).toEqual("Interception Success!"); 145 | }); 146 | 147 | test("Should not display Success message on unsucesfull intercept", () => { 148 | let localProps = createTestProps({ 149 | tabRecord: { 150 | interceptStatus: "" 151 | } 152 | }); 153 | wrapper = shallow(); 154 | expect(wrapper.find("#success-msg").exists()).toBeFalsy(); 155 | }); 156 | 157 | test("on clear button click, should trigger props.clearFields", () => { 158 | let localProps = createTestProps(); 159 | wrapper = mount(); 160 | wrapper.find(".btn-clear").simulate("click"); 161 | expect(localProps.clearFields).toBeCalledWith(1); 162 | }); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interceptor 2 | 3 | [![Build Status](https://travis-ci.org/code-mancers/interceptor.svg?branch=master)](https://travis-ci.org/code-mancers/interceptor) 4 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/code-mancers/interceptor/blob/master/LICENSE) 5 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 6 | 7 | 8 | A browser extension that mocks AJAX request at the browser level so you 9 | can run front ends without really starting a backend server. 10 | 11 | Firefox : [![Mozilla Add-on](https://img.shields.io/amo/rating/xhr-interceptor.svg)](https://addons.mozilla.org/en-US/firefox/addon/xhr-interceptor/) 12 | 13 | Chrome : [![Chrome Web Store](https://img.shields.io/chrome-web-store/rating/enenfaicdcfgcnjmiigcjbmlbaoapnen.svg)](https://chrome.google.com/webstore/detail/interceptor/enenfaicdcfgcnjmiigcjbmlbaoapnen) 14 | 15 | ## Development 16 | 17 | In Chrome 18 | 19 | ``` 20 | $ yarn install 21 | $ yarn run watch 22 | # Install "./dist" directory as "unpacked chrome extension" (google it!) 23 | ``` 24 | 25 | In Firefox 26 | 27 | ``` 28 | $ yarn install 29 | $ yarn run watch 30 | $ yarn global add web-ext 31 | $ web-ext run -s dist -f "/usr/bin/firefox" 32 | ``` 33 | 34 | ## Release 35 | 36 | ``` 37 | $ yarn run build 38 | $ AMO_API_KEY= AMO_API_SECRET= NEW_VERSION=0.3.2 yarn run release 39 | ``` 40 | 41 | 42 | ## How to use 43 | 44 | ### Listening to requests made by the browser 45 | 46 | Once you open the extension popup, it shows a UI as seen below. By default, `Intercept Mode` is `ON`. 47 | 48 | Interceptor extension default popup 49 | 50 | Once you start listening, it shows the count of total AJAX requests in a small badge and list of incoming `XHR` requests 51 | in the popup like this: 52 | 53 | Interceptor extension popup showing a list of AJAX requests 54 | 55 | Request headers for listened requests are listed below the response form as shown in the screenshot. 56 | 57 | A screenshot of Listened requests with request headers 58 | 59 | #### Response data returned by backend server 60 | 61 | Many a times, before defining you mock response text, you would want to look at the response data returned by the server when the request is made. On clicking `Fetch Response` button, the `textarea` gets filled with the response data from the real server as shown below. However, You can completely skip this step and move on to typing out mock response text. 62 | 63 | Fetching data from backend server 64 | 65 | #### Specifying mock response data 66 | 67 | You can click the small arrow beside the URL, which shows a form in which you can specify a response to mock, when the same request is encountered next. You also need to specify the [Content-Type header][content-type] field and [status code][status-code] for the mock response through the dropdown available as shown below. 68 | 69 | Specify mock responses using Interceptor as shown 70 | 71 | Once the above fields are filled and checkbox is checked, click the `INTERCEPT` button. If the interception is successfull, it shows a success message as below: 72 | 73 | Success message shown by Interceptor upon sucessful interception 74 | 75 | You can intercept/mock multiple calls by checking as many checkboxes as you want 76 | 77 | Success message shown by Interceptor upon sucessful interception 78 | 79 | Henceforth the same AJAX request is made by the browser, the browser is given a fake/mock response instead of the real one. 80 | 81 | You can also stop listening for `AJAX calls` by clicking the `STOP LISTENING` button. Requests made henceforth won't be listed on UI. 82 | 83 | The toggle switch is used to disable `INTERCEPTOR`. If the toggle is switched to `OFF` state, it displays a message saying `Interception Disabled` as below. 84 | 85 | Message shown by Interceptor on disabling 86 | 87 | In the `disabled` state, the extension won't mock any previously intercepted calls. Instead all `XHR's` are routed to the server. 88 | The extension's icon beside the url address bar turns red for that particular tab as in screenshot above. 89 | 90 | 91 | To mock the calls again, just toggle the switch to `ON` state, check the requests that are to be mocked and click `INTERCEPT` button. 92 | 93 | This [blogpost](https://crypt.codemancers.com/posts/2018-04-24-intro-to-interceptor/) makes things much more clear. 94 | 95 | ## TODO 96 | 97 | * ~~A user should be able to click on the extension button and see a popup with a list of all AJAX requests.~~ 98 | * A user should be able to "watch" ajax requests using a URL pattern. 99 | * If watched requests are in pre-flight, block everything and ask the user how to handle it. 100 | * ~~The user may choose to let the request pass through or fill in mock response using a form.~~ 101 | * Persist settings for each URL in localStorage. 102 | * ~~Mocked requests should hit a sinon fakeServer.~~ 103 | * ~~User should be able to disable/enable mocking for a page without clearing persisted settings for the URL.~~ 104 | 105 | ## Attribution 106 | 107 | Icons for this projects are used from [Font Awesome](https://fontawesome.com) 108 | 109 | * [Play Circle](https://fontawesome.com/icons/play-circle?style=solid) and [Stop Circle](https://fontawesome.com/icons/stop-circle?style=solid) icons are used from [Font Awesome](https://fontawesome.com/license) 110 | 111 | 112 | ## License 113 | 114 | MIT 115 | 116 | [content-type]: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html 117 | [status-code]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 118 | -------------------------------------------------------------------------------- /app/reducers/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { POPUP_PROPS, Action } from "../types"; 2 | import * as actionType from "../actions"; 3 | 4 | export const INITIAL_POPUP_STATE: POPUP_PROPS = { 5 | tabRecord: {}, 6 | currentUrl: "", 7 | currentTab: -1, 8 | showAddRuleModal: false 9 | }; 10 | 11 | const initialTabProperties = { 12 | enabledStatus: false, 13 | requests: [], 14 | errorMessage: "", 15 | PageDetails: { 16 | currentRowSize: 10, 17 | currentPageNumber: 0 18 | }, 19 | checkedReqs: {}, 20 | isInterceptorOn: true, 21 | requestRecords: {} 22 | }; 23 | 24 | const initialRequestProperties = { 25 | serverResponse: "", 26 | responseError: "", 27 | serverError: "", 28 | contentType: "", 29 | responseText: "", 30 | statusCode: "" 31 | }; 32 | 33 | function extendTabRecords(state: POPUP_PROPS, payload: any, newTabRecords: any) { 34 | return { 35 | ...state, 36 | tabRecord: { 37 | ...state.tabRecord, 38 | [payload.tabId]: { 39 | ...state.tabRecord[payload.tabId], 40 | ...newTabRecords 41 | } 42 | } 43 | }; 44 | } 45 | 46 | function changeRequestUrl(state: POPUP_PROPS, payload: any) { 47 | const requests = [...state.tabRecord[payload.tabId].requests]; 48 | requests[payload.index].url = payload.value; 49 | return { 50 | ...state, 51 | tabRecord: { 52 | ...state.tabRecord, 53 | [payload.tabId]: { 54 | ...state.tabRecord[payload.tabId], 55 | requests: requests 56 | } 57 | } 58 | }; 59 | } 60 | 61 | function requestsReducer(state: POPUP_PROPS, payload: any, newRequest: any) { 62 | return { 63 | ...state, 64 | tabRecord: { 65 | ...state.tabRecord, 66 | [payload.tabId]: { 67 | ...state.tabRecord[payload.tabId], 68 | requests: [...state.tabRecord[payload.tabId].requests, ...newRequest], 69 | requestRecords: { 70 | ...state.tabRecord[payload.tabId].requestRecords, 71 | [payload.request.requestId]: { 72 | ...(state.tabRecord[payload.tabId].requestRecords[payload.tabId] || 73 | initialRequestProperties) 74 | } 75 | } 76 | } 77 | } 78 | }; 79 | } 80 | 81 | function extendRequestRecords(state: POPUP_PROPS, payload: any, newRequestRecords: any) { 82 | return { 83 | ...state, 84 | tabRecord: { 85 | ...state.tabRecord, 86 | [payload.tabId]: { 87 | ...state.tabRecord[payload.tabId], 88 | requestRecords: { 89 | ...state.tabRecord[payload.tabId].requestRecords, 90 | [payload.requestId]: { 91 | ...state.tabRecord[payload.tabId].requestRecords[payload.requestId], 92 | ...newRequestRecords 93 | } 94 | } 95 | } 96 | } 97 | }; 98 | } 99 | 100 | //ACTION CONSTANTS 101 | export const reducer = (state = INITIAL_POPUP_STATE, action: Action) => { 102 | switch (action.type) { 103 | case actionType.INITIALISE_DEFAULTS: 104 | return { 105 | ...state, 106 | tabRecord: { 107 | ...state.tabRecord, 108 | [action.payload.currentTab]: { 109 | ...(state.tabRecord[action.payload.currentTab] || initialTabProperties) 110 | } 111 | }, 112 | currentUrl: action.payload.currentUrl, 113 | currentTab: action.payload.currentTab, 114 | interceptStatus: action.payload.interceptStatus 115 | }; 116 | case actionType.TOGGLE_SHOW_ADD_REQUEST: { 117 | return { 118 | ...state, 119 | showAddRuleModal: action.payload.showAddRuleModal 120 | }; 121 | } 122 | case actionType.ERROR: 123 | return extendTabRecords(state, action.payload, { 124 | errorMessage: action.payload.errorMessage, 125 | enabledStatus: false 126 | }); 127 | case actionType.CLEAR_REQUESTS: 128 | return extendTabRecords(state, action.payload, { 129 | requests: [], 130 | requestRecords: {}, 131 | checkedReqs: {} 132 | }); 133 | case actionType.TOGGLE_CHECKBOX: 134 | return extendTabRecords(state, action.payload, { 135 | checkedReqs: { 136 | ...state.tabRecord[action.payload.tabId].checkedReqs, 137 | [action.payload.reqId]: action.payload.checked 138 | } 139 | }); 140 | 141 | case actionType.RESP_TEXT_CHANGE: 142 | return extendRequestRecords(state, action.payload, { 143 | responseText: action.payload.value 144 | }); 145 | case actionType.STATUSCODE_CHANGE: 146 | return extendRequestRecords(state, action.payload, { 147 | statusCode: action.payload.value 148 | }); 149 | case actionType.CONTENT_TYPE_CHANGE: 150 | return extendRequestRecords(state, action.payload, { 151 | contentType: action.payload.value 152 | }); 153 | case actionType.PAGINATION_CHANGE: 154 | return extendTabRecords(state, action.payload, { 155 | PageDetails: { 156 | ...state.tabRecord[action.payload.tabId].PageDetails, 157 | [action.payload.field]: action.payload.value 158 | } 159 | }); 160 | case actionType.UPDATE_MESSAGE: { 161 | return { ...state, interceptStatus: action.payload.message }; 162 | } 163 | case actionType.UPDATE_INTERCEPTOR_STATUS: 164 | return extendTabRecords(state, action.payload, { 165 | isInterceptorOn: action.payload.value 166 | }); 167 | case actionType.FETCH_DATA_IN_PROGRESS: 168 | return extendRequestRecords(state, action.payload, { 169 | fetching: action.payload.fetching 170 | }); 171 | 172 | case actionType.FETCH_DATA_SUCCESS: 173 | return extendRequestRecords(state, action.payload, { 174 | serverResponse: action.payload.response 175 | }); 176 | case actionType.FETCH_DATA_FAILURE: 177 | return extendRequestRecords(state, action.payload, { 178 | serverError: action.payload.error 179 | }); 180 | case actionType.TOGGLE_LISTENING: { 181 | return extendTabRecords(state, action.payload, { 182 | enabledStatus: action.payload.enabledStatus 183 | }); 184 | } 185 | case actionType.UPDATE_REQUEST: 186 | return requestsReducer(state, action.payload, [action.payload.request]); 187 | case actionType.CHANGE_URL_TABLE: 188 | return changeRequestUrl(state, action.payload); 189 | default: 190 | return state; 191 | } 192 | }; 193 | -------------------------------------------------------------------------------- /app/components/Intercept_Components/InterceptTextBox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ContentEditable from "react-simple-contenteditable"; 3 | 4 | import { RequestHeaderList } from "./RequestHeaderList"; 5 | interface InterceptTextBox { 6 | requestRecords: any; 7 | rowProps: any; 8 | fetchResponse: any; 9 | handleRespTextChange: any; 10 | handleStatusCodeChange: any; 11 | handleContentTypeChange: any; 12 | currentTabId: number; 13 | fetchFailure: any; 14 | } 15 | 16 | export const InterceptTextBox: React.SFC = props => { 17 | const requestId = props.rowProps.checkbox.requestId; 18 | const defaultResponseText = ""; 19 | const defaultStatusCode = "200"; 20 | const defaultContentType = "application/json"; 21 | const responseTextValue = props.requestRecords.responseText || defaultResponseText; 22 | const statusCodeValue = props.requestRecords.statusCode || defaultStatusCode; 23 | const contentTypeValue = props.requestRecords.contentType || defaultContentType; 24 | return ( 25 |
26 |
27 | {props.requestRecords.serverError ? ( 28 |

{props.requestRecords.serverError}

29 | ) : null} 30 |
31 |
32 |
33 | 34 | { 40 | props.handleChangeUrl(value, props.currentTabId, props.index); 41 | }} 42 | /> 43 |
44 |
45 | 46 | 58 |