{"id":13602424,"url":"https://github.com/mitmplay/mitm-play","last_synced_at":"2025-07-23T14:02:21.850Z","repository":{"id":40810066,"uuid":"262723985","full_name":"mitmplay/mitm-play","owner":"mitmplay","description":"Man in the middle using Playwright","archived":false,"fork":false,"pushed_at":"2023-03-13T21:47:20.000Z","size":50709,"stargazers_count":28,"open_issues_count":5,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-22T10:37:05.008Z","etag":null,"topics":["automation","chromium","firefox","man-in-the-middle","mitm","mitm-play","mitmproxy","playwright","puppeteer","webkit"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mitmplay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-05-10T06:10:15.000Z","updated_at":"2025-07-03T20:13:00.000Z","dependencies_parsed_at":"2023-02-14T11:31:22.818Z","dependency_job_id":"159225ca-0c7b-4903-9820-eab3a437d8bd","html_url":"https://github.com/mitmplay/mitm-play","commit_stats":{"total_commits":1563,"total_committers":3,"mean_commits":521.0,"dds":0.01407549584133072,"last_synced_commit":"719a86ab60b44dcfc92da18e873de66f89bc94f1"},"previous_names":[],"tags_count":740,"template":false,"template_full_name":null,"purl":"pkg:github/mitmplay/mitm-play","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitmplay%2Fmitm-play","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitmplay%2Fmitm-play/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitmplay%2Fmitm-play/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitmplay%2Fmitm-play/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitmplay","download_url":"https://codeload.github.com/mitmplay/mitm-play/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitmplay%2Fmitm-play/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266691575,"owners_count":23969181,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","chromium","firefox","man-in-the-middle","mitm","mitm-play","mitmproxy","playwright","puppeteer","webkit"],"created_at":"2024-08-01T18:01:22.804Z","updated_at":"2025-07-23T14:02:21.766Z","avatar_url":"https://github.com/mitmplay.png","language":"JavaScript","readme":"# Man in the middle\n### Using Playwright to intercept traffic in between and do lots of stuff for Developer to exercise\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003emitm-play in action\u003c/b\u003e\u003c/summary\u003e\n\n[![mitm-play](https://raw.githubusercontent.com/mitmplay/user-route/docs/docs/keybr.com-ytube.png)](https://www.youtube.com/watch?v=sXTsy_XxILg)\n\n\u003c/details\u003e\n\n   * [Installation](#installation)\n   * [Features](#features)\n   * [Concept](#concept)\n   * [Object \u0026 function](#object--function)\n   * [Route Sections](#route-section)\n   * [\\_global\\_ Route](#_global_-route)\n   * [~/.mitm-play](#mitm-play)\n   * [HTTP_PROXY](#http_proxy)\n   * [CLI Options](#cli-options)\n   * [Macros](#macros)\n   * [Macro Keys](#macro-keys)\n   * [Persistent](#persistent)\n   * [ws__send](#ws__send)\n   * [User Route](#user-route)\n   * [Use Cases](#use-cases)\n   * [Early Stage](#early-stage)\n\n# Installation\n```bash\nnpm install -g mitm-play\n```\nExecute mitm-play command with demo route, or add `-h` to see help screen:\n```bash\nmitm-play -Gdr  #-OR-\nNODE_OPTIONS='--inspect' mitm-play -Gdr \n```\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\n\n```js\n// create file: ~/user-route/keybr.com/index.js \u0026 add this content:\nconst css = `\nbody\u003ediv,.Body-header,.Body-aside {\n  display: none !important;\n}`;\n\nconst route = {\n  url: 'https://keybr.com',\n  'mock:no-ads': {\n    'cloudflareinsights.com': '',\n    '#201:google.+.com': '',\n    'doubleclick.net': '',\n    'cookiebot.com': '',\n    'btloader.com': '',\n    'pub.network': '',\n  },\n  css: {\n    'GET:no-ads:/assets/[a-z0-9]+': `=\u003e${css}`,\n  },\n}\nmodule.exports = route;\n```\n\n```js\n// create file: ~/user-route/_global_/index.js \u0026 add this content:\nconst route = {\n  'args': {\n    debug: true\n  },\n  'flag': {\n    'ws-connect': true,\n    'ws-message': true,\n  }\n}\nmodule.exports = route;\n```\n\n```bash\n# 1st run will be to save all cli option to 'default'\nmitm-play keyb --delete --save  # --OR--\nmitm-play keyb -ds\n\n# next run should be simple as:\nmitm-play #-OR-\nNODE_OPTIONS='--inspect' mitm-play\n```\nRouting definition having `remove-ads` tag, it will be shown on chrome dev-tools \"mitm-play\" \"tags\" as an option to enabled / disabled rules. You can see the togling process on [this video.](https://www.youtube.com/watch?v=sXTsy_XxILg)\n\n\u003c/details\u003e\n\n# Features\n\n| Feature     | payload      | note\n|-------------|--------------|----------------------------------------\n| `screenshot`| ----------   | DOM specific rules for taking screenshot\n| `noproxy`   | ----------   | array ..of `[domain]` - will serve directly\n| `proxy`     | ----------   | array ..of `[domain]` - will serve using proxy\n| `noskip`    | ----------   | array ..of `[domain]` - forces to noskip\n| `skip`      | ----------   | array ..of `[domain]` - browser will handle it\n| `request`   | __request__  | modify reqs object - call to remote server\n| `mock`      | __response__ | mock resp object - no call to remote server\n| `cache`     | __response__ | 1st call save to local - next call, read from cache\n| `log`       | __response__ | save/log reqs/resp to local - call to remote server\n|             | __response__ | modify resp based on contentType - call remote server\n| =\u003e\u003e         | * `html`     | - response handler (replace / update + JS + ws)\n| =\u003e\u003e         | * `json`     | - response handler (replace / update)\n| =\u003e\u003e         | * `css`      | - response handler (replace / update)\n| =\u003e\u003e         | * `js`       | - response handler (replace / update)\n| `response`  | __response__ | modify resp object - call to remote server\n\n# Concept\nMitm intercept is **hierarchical checking routes**.\n\nFirst check is to **match** domain on the url with **route-folder** as a domain `namespace`.\n\nNext check is to **match** full-url with **regex-routing** of each section/rule. the **regex-routing** having two type:\n* **An Array** [ `nosocket, nonproxy, proxy, noskip, skip` ] \n* **Object Key**: \n  1. General [ `request, mock, cache, log, response` ] \n  2. Specific to content-type [ `html, json, css, js` ] \n\n`if match`, then based on the route section / rules meaning, the next process can be carry over, detail explanations will be on the title of: \"**Route Section**\".\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eStructure Object of Routes:\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * Folder structure:\n * = user-route       // folder\n * \\=== abc.com       // folder\n *    |--- index.js   // file\n *    \\--- index.json // autogenerated file\n*/\n{\n  'abc.com': { // route-folder mapped to object as namespace\n    request: { // sections can be: skip, proxy, \u003cetc...\u003e \n      '/assets/main.js': {     // regex-routing\n        request(reqs, match) { // handler \n          const {body} = reqs;\n          ...\n          return {body}\n        }\n      }\n    }\n  }\n}\n```\n\u003c/details\u003e\u003cbr/\u003e\n\nIf the process of checking is not match, then it will fallback to **\\_global\\_** `namespace` for checking, and the operation is the same as mention in _above paragraph_: `'Next check...'`. \n\nUsually html page load with several assets (image, js \u0026 css) that not belong to the same domain, and to match those type of assets, it use browser headers attributes: `origin` or `referer`, in which will scoping to the `same namespace`.\n\n## Object \u0026 function\nDetail structure of `Object` and `Function` shared accros **Section** \n#### _Objects_\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003ematch\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * match: {\n *   tags       : {},\n *   route      : {}, \n *   contentType: {}, \n *   workspace  : '',/undefined,\n *   namespace  : '', \n *   pathname   : '', \n *   hidden     : true,/false \n *   search     : '',\n *   host       : '',\n *   arr        : [],\n *   url        : '',\n *   key        : '',\n *   log        : '',\n *   typ        : 'cache:tag'\n * }\n*/\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003erequest\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * reqs/request: {\n *   url        : '',\n *   method     : 'GET',/PUT/POST/DELETE \n *   headers    : {}, \n *   oriRef     : '',\n *   body       : '',/null,\n *   browserName: 'chromium',/webkit/firefox\n * }\n*/\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eresponse\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * resp/response: {\n *   url    : '',\n *   status : 200,/302/400/500/etc.. \n *   headers: {},\n *   body   : '',\n * }\n*/\n```\n\u003c/details\u003e\n\n#### _Functions_\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003efile(reqs, match)\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * arguments:\n * - \u003creqs: object\u003e\n * - \u003cmatch: object\u003e\n * \n * return: \u003cfilename: string\u003e/false\n * \n * False value indicate skiping rule\n*/\nfile(reqs, match) {\n  match.path = 'some/path' // superseded match.route.path\n  ...\n  return 'common.js'; //return {path: 'some/path', file: 'common.js'}\n},\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003erequest(reqs, match)\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * arguments:\n * - \u003creqs: object\u003e\n * - \u003cmatch: object\u003e\n * \n * return: \u003creqs: object\u003e\n*/\nrequest(reqs, match) {\n  const {headers} = reqs;\n  headers['new-header'] = 'with some value';\n  ...\n  return {headers};\n},\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eresponse(resp, reqs, match)\u003c/b\u003e\u003c/summary\u003e\n\n```js\n/**\n * arguments:\n * - \u003cresp: object\u003e\n * - \u003creqs: object\u003e\n * - \u003cmatch: object\u003e\n * \n * return: \u003cresp: object\u003e\n*/\nresponse(reqs, reqs, match) {\n  const {headers} = reqs;\n  headers['new-header'] = 'with some value';\n  ...\n  return {headers};\n},\n```\n\u003c/details\u003e\n\n# Route Section\non each route you can add section supported:\n\n\u003cdetails\u003e\u003csummary\u003eSkeleton\u003c/summary\u003e\n\n```js\nroute = {\n  url:     '',\n  urls:    {},\n  title:   '',\n  jsLib:   [],\n  workspace: '',\n  screenshot: {}, //user interaction rules \u0026 DOM-element observer\n  nosocket:[],\n  noproxy: [], \n  proxy:   [], //request with proxy\n  noskip:  [], //start routing rules\n  skip:    [],\n  request: {},\n  mock:    {}, \n  cache:   {},\n  response:{},\n  html:    {},\n  json:    {},\n  css:     {},\n  js:      {},\n  log:     {}, //end routing rules\n}\n```\n\u003c/details\u003e\n\u003cp\u003e\nthe execution order as documented start with `skip`, end with `response`, no need to implement all of routing rules. \n\u003c/p\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eTitle, url, urls, workspace \u0026 jsLib\u003c/b\u003e\u003c/summary\u003e\n\n`Title`: provide basic information about this route.\n\n`Url`: when user enter cli with `1st args`, it will try to find in **`url`**, then open the browser with that **`location`**.\n\n`Urls`: additional search `urls` key, the `1st args` can be split by [`,`], if find more than one, it will  open multi tabs.\n\n`workspace`: will be use as the base folder for `file` option in `Mock` and `Cache`.\n\n`lib`: inject js library into html which having websocket, it can be [`jquery.js`, `faker.js`, `chance.js`, `log-patch.js`, `axe.js`]\n\n```js\nroute = {\n  title: 'Amazon - amazon',\n  url:  'https://www.amazon.com/b?node=229189',\n  urls: {\n    ama1: 'https://www.amazon.com/b?node=229100',\n    ama2: 'https://www.amazon.com/b?node=229111',\n  },\n  workspace: '~/Projects',\n  jsLib: ['chance.js'],\n};\n// cli: mitm-play ama -dpsr='.' \n// search: 'ama' and it will open two browser tabs\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eScreenshot\u003c/b\u003e\u003c/summary\u003e\n\nCapture/Screeshot when user *click* specific DOM-Element *match* with `selector` or state-change, like DOM-Element getting *insert* or *remove* and match **selector** inside `observer` key.\n\nBelow example show three selector in `observer`:\n*  *'.field.error'* -\u003e **filename**: field-error -\u003e **state**: `insert` or `remove`\n*  *'.input.focus'* -\u003e **filename**: input -\u003e **state**: `insert` or `remove`\n*  *'.panel.error'* -\u003e **filename**: panel-error -\u003e **state**: `insert`\n\nCaveat: `observer` is an *experimental feature*, take it as a grain salt, expectation of selector should be like toggling and it need a unique match to the DOM-Element, *please do test on chrome-devtools before reporting a bug*.\n\nCaveat 2: this `Screenshot` rule(s), required successful injection of websocket client to html document, if it not success (error can be seen on chrome dev-tools),might be *content-security-policy* restriction. \n\nCaveat 3: process screenshot sometime take times and for SPA, transition between page usually instantly and it lead to capturing next page, even if the trigger come from button in previouse page, there is a CLI option: -z/--lazy to delay click action for about ~400ms \n```js\nscreenshot: {\n  selector: '[type=button],[type=submit],button,a', //click event\n  observer: {\n    /***\n     * selector must be uniq, represent not in the dom \n     * state change couse element tobe insert or remove,\n     * or can be just class change \n    */\n    '.field.error': 'field-error:insert,remove',\n    '.input.focus': 'input:insert,remove',\n    '.panel.error': 'panel-error:insert',\n  },\n  at: 'sshot', //'^sshot' part of log filename\n},\n```\n`at` is a partion of filename and having a simple rule attach on it. Guess what is it?.\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eNosocket\u003c/b\u003e\u003c/summary\u003e\n\nNo `WebSocket` Injection to **`html`**, `mitm-play` will process further.\n```js\nnosocket: ['sso'],\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eNoproxy\u003c/b\u003e\u003c/summary\u003e\n\nif proxy config was set to all request/response, `noproxy` will exclude it from proxy. Example below will set domain nytimes.com with direct access and the rest will go thru proxy. \n```js\n// HTTP_PROXY env need to be set, cli: --proxy .. --noproxy ..\nnoproxy: ['nytimes.com'],\nproxy:   ['.+'],\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eProxy\u003c/b\u003e\u003c/summary\u003e\n\nCertain domain will go thru proxy, `proxy` \u0026 `noproxy` will make sanse if command line contains -x/--proxy\n```js\n// HTTP_PROXY env need to be set, cli: --proxy ..\nproxy: [\n  'google-analytics.com',\n],\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eNoskip\u003c/b\u003e\u003c/summary\u003e\n\nForces to some domains not to be skip it \n```js\nnoskip: ['wp-admin'],\nskip  : ['.+'], // put it in on global routes\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eSkip\u003c/b\u003e\u003c/summary\u003e\n\nSkipping back **`url`** to the browser if partion of **`url`** match text in array of `skip` section, `mitm-play` will not process further.\n```js\nskip: ['.+'],\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eRequest\u003c/b\u003e\u003c/summary\u003e\n\nManipulate **request** with `request` function\n```js\nrequest: {\n  'GET:/disqus.com/embed/comments/': {\n    request(reqs, match) {\n      const {headers} = reqs;\n      headers['new-header'] = 'with some value';\n      ...\n      return {headers};\n    },\n    session: true, // optional - set session id\n  }\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eMock\u003c/b\u003e\u003c/summary\u003e\n\nMock the **response**.\n\nBasic rule:\n\nReplace **response body** with **the matcher** value \n```js\nmock: {'/mock': 'Hi!'},\n```\nReplace  **response body** with content from file\n```js\nmock: {'/mock1': {file: 'mocks/mock1.json'}},\n```\n```js\nmock: {'/mock2': {path: 'mocks', file: 'mock2.html'}}, // match.route.path\n```\nManipulate **response** with `response` *function*\n```js\nmock: {\n  '/mock3': {\n    file(reqs, match) {\n      match.path = 'mocks' // superseded match.route.path\n      return 'mock2.html' //return {path: 'some/path', file: 'filename'}\n    },\n    response(resp, reqs, match) {\n      let {body} = resp\n      body += '\u003ch2\u003ethere!\u003c/h2\u003e'\n      return {body} // {status, headers, body} or false to skip\n    },\n    log: true, // optional - enable logging\n    ws: true,  // inject web socket (html)\n  },\n},\n```\nReplace response body with content from remote\n```js\n'/mock4': {path: 'https://www.lipsum.com/feed', file: 'html'}, \n```\nBelow is the logic of `file` getting translate combine with `path` or `workspace`, if `workspace` exists, and `file` value not start with dot(`.`), it will use `workspace` (ie: `${workspace}/${file}`) and the `path` will be ignore.\n```js\nmock: {\n  'mitm-play/twitter.js': {\n    file: 'relative/to/workspace/file.html', // --OR--\n    // file: '../relative/to/route/folder/file.html',\n    // file: './relative/to/route/folder/file.html',\n    // file: '~/relative/to/home/folder/file.html',\n    // file: (reqs, match) =\u003e 'filename'\n  },\n},\n```\nConcatenation of JS code `js` at the end of **the mock body**\n```js\nconst unregisterJS = () =\u003e {\n  ...\n  console.log('unregister service worker')\n};\n\nmock: {\n  'mitm-play/twitter.js': {\n    js: [unregisterJS],\n  },\n},\n```\nIf both options are defined: `response`, `js`, `js` will be ignored.\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eCache\u003c/b\u003e\u003c/summary\u003e\n\nSave the first request to your local disk so next request will serve from there.\n```js\ncache: {\n  'amazon.com': {\n    contentType: ['javascript', 'image'], //required!\n    jsonHeader: ['nel'],// convert hearder(s) to json\n    querystring: true,  // hash of unique file-cache\n    hidden: true,       // optional - no consolo.log\n    log: true,          // optional - enable logging\n    path: './api',      // optional cache file-path\n    file: ':1.png',     // optional cache file-name\n    tags: 'js-img',     // optional route by tags\n    at: 'mycache',      // part of filename\n  }\n},\n```\nlogic for `file` is the same as in `mock`, if `workspace` exists and `file` value not start with dot(`.`), it will use `workspace` (ie: `${workspace}/${file}`) and the `path` will be ignore.\n```js\ncache: {\n  'amazon.com': {\n    file: 'relative/to/workspace/file.html', // --OR--\n    // file: '../relative/to/route/folder/file.html',\n    // file: './relative/to/route/folder/file.html',\n    // file: '~/relative/to/home/folder/file.html',\n    // file: (reqs, match) =\u003e 'filename'\n  },\n},\n```\n`cache` support `response` function, it means the result can be manipulate first before send to the browser.\n```js\ncache: {\n  'amazon.com': {\n    contentType: ['json'], //required! \n    response(resp, reqs, match) {\n      const {body} = resp;\n      ...\n      return {body} // {status, headers, body} or false to skip\n    },    \n  }\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eResponse\u003c/b\u003e\u003c/summary\u003e\n\nManipulate **response** with `response` function\n```js\nresponse: {\n  '.+': {\n    response(resp) {\n      const {headers} = resp;\n      headers['new-header'] = 'with some value';\n      ...\n      return {headers};\n    },\n    tags: 'all-response',\n  }\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eHtml\u003c/b\u003e\u003c/summary\u003e\n\nManipulate the response.\n\nBasic rule: \n\nReplace **response body** with **some** value \n```js\nhtml: {'twitter.net': ''},\n```\n\nInsert `js` script element into specific area in html document:\n* el: 'head' \u0026nbsp; // default, no need to add `el` key\n* el: 'body'\n```js\nhtml: {\n  'https://keybr.com/': {\n    // el: 'head', // JS at \u003chead\u003e \n    js: [()=\u003econsole.log('Injected on Head')],\n  },\n},\n```\nInsert `\u003cscript src=\"...\"\u003e\u003c/script\u003e` into `\u003chead\u003e` section\n```js\nhtml: {\n  'https://keybr.com/': {\n    src: ['http://localhost:/myscript.js'],\n    ws: true, // inject web socket\n  },\n},\n```\nManipulate **response** with `response` *function*\n```js\nhtml: {\n  'https://keybr.com/': {\n    response(resp, reqs, match) {\n      const {body} = resp;\n      ....\n      return {body} // {status, headers, body} or false to skip\n    },\n    tags: 'response' // enable/disable route by tags\n    hidden: true, // optional - no consolo.log\n  },\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eJson\u003c/b\u003e\u003c/summary\u003e\n\nManipulate the response.\n\nBasic rule: \n\nReplace **response body** with **some** value \n```js\njson: {'twitter.net': '{}'},\n```\n\nManipulate **response** with `response` *function*\n```js\njson: {\n  'twitter.com/home': {\n    response(resp, reqs, match) {\n      const {body} = resp;\n      ....\n      return {body} // {status, headers, body} or false to skip\n    },\n    tags: 'json-manipulate',\n  },\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eCss\u003c/b\u003e\u003c/summary\u003e\n\nManipulate the response.\n\nBasic rule: \n\nReplace **response body** with **some** value -or- add to the end of response body by adding FAT arrow syntax `=\u003e${style}`\n```js\nconst style = 'body: {color: red}';\n...\ncss: {'twitter.net': style}, //or `=\u003e${style}`\n```\n\nManipulate **response** with `response` *function*\n```js\ncss: {\n  'twitter.com/home': {\n    response(resp, reqs, match) {\n      const {body} = resp;\n      ....\n      return {body} // {status, headers, body} or false to skip\n    },\n    tags: 'css-manipulate',\n  },\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eJs\u003c/b\u003e\u003c/summary\u003e\n\nManipulate the response.\n\nBasic rule: \n\nReplace **response body** with **some** value -or- add to the end of response body by adding FAT arrow syntax `=\u003e${style}`\n```js\nconst code = 'alert(0);'\n...\njs: {'twitter.net': code}, //or `=\u003e${code}`\n```\n\nManipulate **response** with ~~`response`~~ *function*\n```js\njs: {\n  'twitter.com/home': {\n    response(resp, reqs, match) {\n      const {body} = resp;\n      ....\n      return {body} // {status, headers, body} or false to skip\n    },\n    tags: 'js-manipulate',\n  },\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eLog\u003c/b\u003e\u003c/summary\u003e\n\nSave the response to your local disk. by default contentType `json` will log complete request / response, for different type default log should be response payload. \n\nSpecial usacase like google-analytic will send contentType of `gif` with [GET] request, and response payload is not needed, there is an option `log` to force log with json complete request / response.  \n```js\nlog: {\n  'amazon.com': {\n    contentType: ['json'],\n    jsonHeader: ['nel'], // convert hearder(s) to json\n    tags: 'json-bo'      // optional route by tags\n    at: 'myjson',        // part of log filename\n  },\n  'google-analytics.com/collect': {\n    contentType: ['gif'],\n    log: true,      // '\u003cremove\u003e'\n  }\n},\n```\n`log` support `response` function, it means the result can be manipulate first before send to the browser or save to logs file.\n```js\nlog: {\n  'amazon.com': {\n    contentType: ['json'], //required! \n    response(resp, reqs, match) {\n      const {body} = resp;\n      ...\n      return {body} // {status, headers, body} or false to skip\n    },\n  }\n},\n```\n\u003c/details\u003e\n\n# \\_global\\_ Route\n\nA special route to handle global scope (without namespace) \n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eCommon route rules\u003c/b\u003e\u003c/summary\u003e\n\n```js\n_global_ = {\n  jsLib:   [],\n  skip:    [], //start routing rules\n  proxy:   [], //request with proxy\n  noproxy: [], \n  nosocket:[],\n  request: {},\n  mock:    {}, \n  cache:   {},\n  log:     {},\n  html:    {},\n  json:    {},\n  css:     {},\n  js:      {},\n  response:{}, //end routing rules\n}\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eArgs \u0026 flag\u003c/b\u003e\u003c/summary\u003e\n\nTwo additional Section only appear in _\\_global\\__\n\n`args`, `flag` and it can be served as a section-tags\n\n```js\n_global_ = {\n  args: { // part of cli options\n    activity,  // rec/replay cache activity*\n    cookie,    // reset cookies expire date*\n    fullog,    // show detail logs on each rule*\n    lazyclick, // delay ~700ms click action*\n    nosocket,  // no websocket injection to html page*\n    nohost,    // set logs without host name*\n    nourl,     // set logs without URL*\n    csp,       // relax CSP unblock websocket*\n  }\n}\n```\n```js\n_global_ = {\n  flag: { // toggle to show/hide from console.log()\n    'referer-reqs': true,\n    'no-namespace': true,\n    'ws-broadcast': false, // true if --verbose\n    'ws-connect': false,   // true if --verbose\n    'ws-message': false,   // true if --verbose\n    'frame-load': false,   // true if --verbose\n    'page-load': false,    // true if --verbose\n    'mitm-mock': false,    // true if --verbose\n    'file-log': false,     // true if --verbose\n    'file-md': false,      // true if --verbose  \n    silent:   false,       // true: hide all\n    skip:     false,\n    nosocket: true,\n    request:  true,\n    mock:     true,\n    cache:    true,\n    log:      true,\n    html:     true,\n    json:     true,\n    css:      true,\n    js:       true,\n    response: true,\n  }\n}\n```\n\u003c/details\u003e\n\n# ~/.mitm-play\nBy default all save file are on the `~/.mitm-play` profile folder.\n\n# HTTP_PROXY\nmitm-play support env variable **HTTP_PROXY** and **NO_PROXY** if your system required proxy to access internet. Please check on `CLI Options \u003e -x --proxy` section for detail explanation. \n\n# CLI Options\nwhen entering CLI commands, `mitm-play`  support two kind of arguments: \n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003emitm-play [args] [-options]\u003c/b\u003e\u003c/summary\u003e\n\n* `args`:\n  * **1st** for searching url/urls\n  * **2nd** for loading profile\n* `options`.\n```bash\n# syntax\n$ mitm-play [args] [-options]\n\n# create 'secure' profile with -s/--save option # OR\n$ mitm-play yahoo --lazyclick --incognito -s='secure'\n$ mitm-play yahoo -zts='secure'\n\n# search yahoo route and use 'secure' profile \u0026 add -k/--cookie option \n$ mitm-play yahoo secure -k\n\n# if no profile, fallback to 'default'\n$ mitm-play yahoo --cookie\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-h --help\u003c/b\u003e\u003c/summary\u003e\n\nTo show all the options Command Line Interface (CLI). this option can be arbitrary position on cli, the result should be always display this messages:\n\n```bash\n$ mitm-play -h  \u003cOR\u003e\n$ mitm-play --help\n\n  Usage: mitm-play [args] [options]\n\n  args:\n    1st for searching url/urls\n    2nd for loading profile\n\n  options:\n    -h --help          show this help\n    -u --url           go to specific url\n    -s --save          save as default \u003cprofl\u003e\n    -r --route         userscript folder routes\n    -a --activity      rec/replay cache activity*\n    -b --basic         login to http authentication\n    -c --clear         clear/delete cache \u0026 log(s)\n    -d --devtools      show chrome devtools on start\n    -e --device        resize to mobile screen device\n    -f --fullog        show detail logs on each rule*\n    -i --insecure      accept insecure cert in nodejs env\n    -j --jformat       JSON save as human readable format\n    -n --nosocket      no websocket injection to html page*\n    -o --offline       console log withount new-line\n    -k --cookie        reset cookies expire date*\n    -l --light         unset devtools dark mode\n    -g --group         create cache group/rec\n    -p --csp           relax CSP unblock websocket*\n    -t --incognito     set chromium incognito\n    -w --worker        enable service worker\n    -x --proxy         a proxy request\n    -z --lazyclick     delay ~700ms click action*\n\n    -A --a11y          axe-core a11y checker\n    -D --debug         show Playwright debugger\n    -E --websecure     enable web security \n    -G --nogpu         set chromium without GPU\n    -H --nohost        set logs without host name*\n    -L --showsql       show sqlite generated commands\n    -R --redirect      set redirection: true/false/manual\n    -Q --nosql         disabling persist data using sqlite\n    -S --session       sqlite session from requst header\n    -U --nourl         set logs without URL*\n    -V --verbose       show more detail of console log\n    -X --proxypac      set chromium proxypac\n\n    -C --chromium      run chromium browser\n    -F --firefox       run firefox browser\n    -W --webkit        run webkit browser\n\n  * _global_.config.args\n    \n  v0.10.xxx\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-u --url\u003c/b\u003e\u003c/summary\u003e\n\nOpen Browser to specific `URL`\n\n```bash\n$ mitm-play -u='https://google.com'  \u003cOR\u003e\n$ mitm-play --url='https://google.com'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-s --save\u003c/b\u003e\u003c/summary\u003e\n\nSave CLI options with `default`  or named so later time you don't need to type long CLI options\n\n```bash\n$ mitm-play -s  \u003cOR\u003e\n$ mitm-play --save\n  \u003cOR\u003e\n$ mitm-play -s='google'  \u003cOR\u003e\n$ mitm-play --save='google'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-r --route\u003c/b\u003e\u003c/summary\u003e\n\nSpecify which folder contains routes config\n\n```bash\n$ mitm-play -r='../user-route'  \u003cOR\u003e\n$ mitm-play --route='../user-route'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-a --activity\u003c/b\u003e\u003c/summary\u003e\n\nFlag the caching with sequences, they are three mode of activity:\n*  `rec:activity`  to record cache w/ `seq`, all cache always recorded\n*  `mix:activity`  to record cache w/ `seq`, non `seq` behave as std cache \n*  `play:activity` to replay cache w/ `seq`, non `seq` behave as std cache\n\nTag `activity` need to be add to **html - rule** to indicate the point when `sequences` cached will be start.\n\n```bash\n$ mitm-play -a='rec:activity'  \u003cOR\u003e\n$ mitm-play --activity='rec:activity'\n```\n\nThe first step is to record the flow and do the navigation\n```bash\n$ mitm-play -a='rec:activity'\n```\n\nNext step is to replay the flow \n```bash\n$ mitm-play -a='play:activity'\n```\n\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-b --basic\u003c/b\u003e\u003c/summary\u003e\n\nWhen page required HTTP Authentication, this parameters will be passs to the newly created Page Context with login and password supplied to this params\n\n```bash\n$ mitm-play -b='MYCREAD:MYPASSWORD'  \u003cOR\u003e\n$ mitm-play --basic='MYCREAD:MYPASSWORD'\n```\n\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-c --clear\u003c/b\u003e\u003c/summary\u003e\n\nDelete logs or cache, can be all or specific one\n\n```bash\n$ mitm-play -c  \u003cOR\u003e\n$ mitm-play --clear\n  \u003cOR\u003e\n$ mitm-play -c='log'  \u003cOR\u003e\n$ mitm-play --clear='log'\n  \u003cOR\u003e\n$ mitm-play -c='cache'  \u003cOR\u003e\n$ mitm-play --clear='cache'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-d --devtools\u003c/b\u003e\u003c/summary\u003e\n\nShow chrome devtools on start up on ech tabs\n\n```bash\n$ mitm-play -d  \u003cOR\u003e\n$ mitm-play --devtools\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-e --device\u003c/b\u003e\u003c/summary\u003e\n\nResize screen to specific mobile device (still buggy)\n\n```bash\n$ mitm-play -e  \u003cOR\u003e\n$ mitm-play --device\n  \u003cOR\u003e\n$ mitm-play -e='iPhone 11 Pro'  \u003cOR\u003e\n$ mitm-play --device='iPhone 11 Pro'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-i --insecure\u003c/b\u003e\u003c/summary\u003e\n\nSet NodeJS to operate within insecure / no https checking \n\n```bash\n$ mitm-play -i  \u003cOR\u003e\n$ mitm-play --insecure\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-j --jformat\u003c/b\u003e\u003c/summary\u003e\n\nSet Saving Json with human readable format\n\n```bash\n$ mitm-play -j  \u003cOR\u003e\n$ mitm-play --jformat\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-l --light\u003c/b\u003e\u003c/summary\u003e\n\nunset devtools dark mode, this option effected only when theme set to `System preference`.\n\n```bash\n$ mitm-play -l  \u003cOR\u003e\n$ mitm-play --light\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-n --nosocket\u003c/b\u003e\u003c/summary\u003e\n\nIf only the params with no value, it will act as No Injection on HTML Page, meaning no open websocket on the page\n\n```bash\n$ mitm-play -n  \u003cOR\u003e\n$ mitm-play --nosocket\n```\nif params contain value _off_ ie: `-n=off`, there will be Injection into HTML Page with no open websocket connection, this options is to get alternative `for macros automation tobe send via [POST] request`.\n\n```bash\n$ mitm-play -n=off  \u003cOR\u003e\n$ mitm-play --nosocket=off\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-o --offline\u003c/b\u003e\u003c/summary\u003e\n\nchange console.log to print the logs only when the log-message is unique from the previous log\n\n```bash\n$ mitm-play -o  \u003cOR\u003e\n$ mitm-play --offline\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-k --cookie\u003c/b\u003e\u003c/summary\u003e\n\nSet proper cache retriver with an update expiry of the cookies\n\n```bash\n$ mitm-play -k  \u003cOR\u003e\n$ mitm-play --cookie\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-g --group\u003c/b\u003e\u003c/summary\u003e\n\nAdd group name to file cache/logs, if necessary when large capturing is done and difficult to check the files. \n\nThere is an option `at` on the rules of `cache`/`log` for additional filename grouping path.\n\n```bash\n$ mitm-play -g='mygroup'  \u003cOR\u003e\n$ mitm-play --group='mygroup'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-t --incognito\u003c/b\u003e\u003c/summary\u003e\n\nBy Default program will run in normal browser, adding this option will result in Incognito mode.\n\n```bash\n$ mitm-play -t  \u003cOR\u003e\n$ mitm-play --incognito\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-w --worker\u003c/b\u003e\u003c/summary\u003e\n\nenable service worker, current release playwirght cannot intercept request that came from service worker.\n\n```bash\n$ mitm-play -w  \u003cOR\u003e\n$ mitm-play --worker\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-x --proxy\u003c/b\u003e\u003c/summary\u003e\n\nSome traffict with domain match to proxy section will use proxy.\n\nthis option serving two kind of needs:\n1. if --proxy without value, mitm-play traffict will get thru proxy. Proxy configuration will get from ENV variable.\n2. if --proxy with string domain, all (mitm-play or browser) traffict will get thru proxy. (ie: `--proxy`='http://username:pass@my.proxy.com')  \n\n```bash\n$ mitm-play -x  \u003cOR\u003e\n$ mitm-play --proxy\n  \u003cOR\u003e\n$ mitm-play -x='http://username:pass@my.proxy.com'  \u003cOR\u003e\n$ mitm-play --proxy='http://username:pass@my.proxy.com'\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-z --lazyclick\u003c/b\u003e\u003c/summary\u003e\n\nDelay click action ~700ms or you can provide value in milisecond, to provide enough time for screenshot to be taken\n\n```bash\n$ mitm-play -z  \u003cOR\u003e\n$ mitm-play --lazyclick\n  \u003cOR\u003e\n$ mitm-play -z=400  \u003cOR\u003e\n$ mitm-play --lazyclick=400\n```\n\u003c/details\u003e\u003chr/\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e--csp\u003c/b\u003e\u003c/summary\u003e\n\nUpdate CSP header on Html Page injected with wws-client.js to unblock Websocket communication\n\n```bash\n$ mitm-play --csp\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-A --a11y\u003c/b\u003e\u003c/summary\u003e\n\nEnable *Axe-core* `a11y checker`, when actiaved, \u003cb\u003ebuttons to check a11y\u003c/b\u003e visible on top-left side of screen, click the button or use short-cut [`Ctl`]+[`Alt`]+**(**[`yyy`] or [`yy`] or [`y`] or [`c`]**)** to execute :\n\n* `strict-[yyy]` - most stricted rules \n* `wcag:AA[yy-]` - WCAG AA rules\n* `a11y---[y--]` - Base Axe-core rules\n* `clear--[c--]` - Clear/reset the page\n\nWhen *Axe-core* `a11y checker` is finished, it will show result in:\n* `2-border`: \u003cb style='color:red;'\u003eViolation\u003c/b\u003e, \u003cb style='color:blue;'\u003eWcag:AAA\u003c/b\u003e, \u0026 \u003cb style='color:greenyellow;'\u003eBest-practice\u003c/b\u003e\n* `1-border`: \u003cb style='color:fuchsia;'\u003eIncomplete\u003c/b\u003e \n\n```bash\n$ mitm-play -A  \u003cOR\u003e\n$ mitm-play --a11y\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-D --debug\u003c/b\u003e\u003c/summary\u003e\n\nMore information will be shown in console.log from `DEBUG=pw:api`, including info from Mitm-play debug logs.  \n\n```bash\n$ mitm-play -D \u003cOR\u003e #pw:api\n$ mitm-play -D=b \u003cOR\u003e\n$ mitm-play --debug=bc\n```\nOption can having combine chars, lowercase represent sepecific Playwright type of logs, but if all Playwiright, use \"V\"   \n|char|value      |\n|:--:|-----------|\n| V  |pw:*       | \n| a  |pw:api     |\n| b  |pw:browser |\n| c  |pw:channel*|\n| p  |pw:protocol|\n| B  |\\*browser\\*  |\n| F  |fetch req-H|\n| P  |page load  |\n| S  |sqlite logs|\n| W  |websocket  |\n\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-E --websecure\u003c/b\u003e\u003c/summary\u003e\n\nEnable web security  \n\n```bash\n$ mitm-play -E  \u003cOR\u003e\n$ mitm-play --websecure\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-G --nogppu\u003c/b\u003e\u003c/summary\u003e\n\nNecessary option for [Macbook owner](https://discussions.apple.com/thread/250878229).\n\nOptions can be added with value -G=all to disabled all gpu (might hang notebook)\n\n```bash\n$ mitm-play -G  \u003cOR\u003e\n$ mitm-play --nogpu\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-H --nohost\u003c/b\u003e\u003c/summary\u003e\n\nset logs without host name\n\n```bash\n$ mitm-play -H  \u003cOR\u003e\n$ mitm-play --nohost\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-L --showsql\u003c/b\u003e\u003c/summary\u003e\n\nTo switch on / show sqlite generated syntax.\n\n```bash\n$ mitm-play -L  \u003cOR\u003e\n$ mitm-play --showsql\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-R --redirect\u003c/b\u003e\u003c/summary\u003e\n\nChange mechanism of redirection\n\n```bash\n$ mitm-play -R  \u003cOR\u003e\n$ mitm-play --redirect\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-U --nourl\u003c/b\u003e\u003c/summary\u003e\n\nset logs without URL\n\n```bash\n$ mitm-play -U  \u003cOR\u003e\n$ mitm-play --nourl\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-V --verbose\u003c/b\u003e\u003c/summary\u003e\n\nAdd additional info in console.log\n\n```bash\n$ mitm-play -V  \u003cOR\u003e\n$ mitm-play --verbose\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-X --proxypac\u003c/b\u003e\u003c/summary\u003e\n\nWhen network on your having a proxypac settings, might be usefull to use the same. This option only in Chromium\n\n```bash\n$ mitm-play -X='w3proxy.netscape.com:8080'  \u003cOR\u003e\n$ mitm-play --proxypac='w3proxy.netscape.com:8080'\n```\n\u003c/details\u003e\u003chr/\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-C --chromium\u003c/b\u003e\u003c/summary\u003e\n\nLaunch Chromium browser\n\n```bash\n$ mitm-play -C  \u003cOR\u003e\n$ mitm-play --chromium\n```\n\n### Preset either **`chrome`** or **`msedge`** \nIf in the system having stock browser of chrome or msedge\n  * chrome\n  * msedge\n  * chrome-dev\n  * msedge-dev\n  * chrome-beta\n  * msedge-beta\n```bash\n$ mitm-play -C=\"chrome\"  \u003cOR\u003e \n$ mitm-play --chromium=\"chrome\"\n```\n### Can be a path to Chrome installation ie on MAC\n```bash\n$ mitm-play -C=\"/Applications/Google\\ Chrome.app\"  \u003cOR\u003e \n$ mitm-play --chromium=\"/Applications/Google\\ Chrome.app\"\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-F --firefox\u003c/b\u003e\u003c/summary\u003e\n\nLaunch Firefox browser\n\n```bash\n$ mitm-play -F  \u003cOR\u003e\n$ mitm-play --firefox\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003e-W --webkit\u003c/b\u003e\u003c/summary\u003e\n\nLaunch Webkit browser\n\n```bash\n$ mitm-play -W  \u003cOR\u003e\n$ mitm-play --webkit\n```\n\u003c/details\u003e\n\n# Macros\nWhen creating rule for specific website site (ie: **autologin to gmail**), inside folder you can add `macros.js` to contains what automation need to be run. macros is a Javascript getting injected into the browser, by default if there is a html request then this macro will be included on the injection. To run different macros in the same SPA, just create another a `named-macros` ie: login -\u003e `login@macros.js` and to load that macro, URL need to have a query params of '?mitm=login'.  \n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eExample\u003c/b\u003e\u003c/summary\u003e\n\n```bash\n# folder\n./accounts.google.com/index.js\n./accounts.google.com/_macros_/macros.js\n./accounts.google.com/_macros_/login@macros.js\n```\n```js\n// .../_macros_/macros.js\nmodule.exports = () =\u003e {\n  const observeOnce = async function() {\n    console.log('Getting execute one time')\n  }\n  return {\n    '^/signin/v2/identifier?'() {\n      console.log('login to google account...!');\n      window.mitm.autofill = [\n        '#identifierId =\u003e myemailname',\n        '#identifierId -\u003e press ~\u003e Enter',\n      ];\n    },\n    '^/signin/v2/challenge/pwd?'() {\n      window.mitm.autofill = [\n        'input[type=\"password\"] =\u003e password',\n        'input[type=\"password\"] -\u003e press ~\u003e Enter',\n      ];\n      // executed when DOM changes, use MutationObserver event\n      // postfix \"Once\" indicate one-time execution\n      return observeOnce\n    }\n  }\n}\n```\n```js\n// will be send to playwright to execute when user click button \"Autofill\"\nwindow.mitm.autofill = [...]\n\n// it will run on interval 500ms\nwindow.mitm.autointerval = () =\u003e {...};\n\n// additinal buttons to be visible on the page top-right\n// buttons can be toggle show / hide by clicking [Ctrl] + [SHIFT]\nwindow.mitm.autobuttons = {\n  'one|blue'() {console.log('one')},\n  'two|green'() {console.log('two')}\n}\n\n// A macro keys can be set as a hotkey!\nwindow.mitm.macrokeys = {...}\n```\n\u003c/details\u003e\n\n# Macro Keys\nA hot keys that can be press on specific page and it will do similar thing with _a macro from mechanical keyboard_, except its generated from injected mitm-play `macros.js`, \n\nExample below show a defined macro keys: `code:KeyA` or `code:KeyP` \u0026 To activate, it need to press combination buttons of `Ctrl` **+** `Alt` **+** `KeyA`/`KeyP`. \n\nlist of `event.code` : https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eExample\u003c/b\u003e\u003c/summary\u003e\n\n```js\n// .../_macros_/macros.js\nmodule.exports = () =\u003e {\n  return {\n    '^/signin/v2/identifier?'() {\n      window.mitm.macrokeys = {\n        'code:KeyA'() {\n          alert('Alert KeyA')\n        }\n      }\n      // -- OR --\n      window.mitm.fn.hotKeys({\n        'code:KeyP'() {\n          // chance is a javascript faker defined in jsLib\n          const name = chance.email().split('@')[0];\n          return [\n            `=\u003e ${name}@mailinator.com`,\n            '-\u003e press ~\u003e Enter',\n          ]  \n        }\n      })\n    }\n  }\n}    \n```\nAutomation commands return from `KeyP` function don't include selector, means it will run from current input focused.\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eVariations\u003c/b\u003e\u003c/summary\u003e\n\n### Recomended macro keys\nCombination `Ctrl + Alt + ...` will work on `Mac`/`Windows`.\n\nSuport all `event.code` \u0026 lowercase `event.key`\n```js\nwindow.mitm.macrokeys = {\n  'key:a'()          { console.log('key in: Ctrl + Alt + a') }, // take presedance over code:KeyA\n  'key:ab'()         { console.log('key in: Ctrl + Alt + ab') },// take presedance over code:KeyA:KeyB\n  'code:KeyA'()      { console.log('key in: Ctrl + Alt + KeyA') },\n  'code:KeyA:KeyB'() { console.log('key in: Ctrl + Alt + KeyA:KeyB') },\n}\n```\nFeature to provide shortcut with option of `_keys` as condition logic.\n\nif `Shift key` **pressed**, it will serve as `saving the key` into `windows.mitm.lastKey._keys`. \n\nIe: how to type shortcut: KeyL with same keys: 'one' save to `windows.mitm.lastKey._keys`:\n```js\n* press:   `Ctrl + Alt + Shift + one`, then\n* release: `Shift` and press: `KeyL`\n// complete press/release on oneliner\n* press: `Ctrl + Alt + Shift + one` release: `Shift` press: `KeyL`\n```\n\n### Not Recomended macro keys - may conflict with reserved keys on OS/Chrome\nConflict with Chrome shortcut keys or in Windows conflict with `Ctrl + J`\n\nSuport all `event.code` \u0026 `event.key`\n```js\nwindow.mitm.macrokeys = {\n  'key:\u003ca\u003e'()          { console.log('key in: .... + Ctrl + a') }, // take presedance over code:KeyA\n  'key:\u003cA\u003e'()          { console.log('key in: .... + Ctrl + A') }, // take presedance over code:KeyA\n  'key:\u003caA\u003e'()         { console.log('key in: .... + Ctrl + aA') },// take presedance over code:KeyA:KeyA\n  'code:\u003cKeyA\u003e'()      { console.log('key in: .... + Ctrl + KeyA') },\n  'code:\u003cKeyA:KeyA\u003e'() { console.log('key in: .... + Ctrl + KeyA:KeyA') },\n}\n```\n\n### Not Recomended macro keys - may conflict with reserved keys on OS/Chrome\nIn windows conflict with `Alt + D`, unless need to combine with Shift ie: `Shift + Alt + D`\n\nSuport all `event.code` \u0026 `event.key`\n```js\nwindow.mitm.macrokeys = {\n  'key:{a}'()          { console.log('key in: .... + Alt + a') }, // take presedance over code:KeyA\n  'key:{A}'()          { console.log('key in: .... + Alt + A') }, // take presedance over code:KeyA\n  'key:{aA}'()         { console.log('key in: .... + Alt + aA') },// take presedance over code:KeyA:KeyA\n  'code:{KeyA}'()      { console.log('key in: .... + Alt + KeyA') },\n  'code:{KeyA:KeyA}'() { console.log('key in: .... + Alt + KeyA:KeyA') },\n}\n```\n\u003c/details\u003e\n\n# Persistent\n`isomorphic - persistent` is currently implement as a global function under namespace `mitm.fn.sql....`:\n\n\u003cdetails\u003e\u003csummary\u003emitm.fn.sqlList - retriving records\u003c/summary\u003e\n\nwhen params is a string, should be sql like statement `where condition` (`no need to put quote`) \nwith an option of `orderby`, `the order orientation` need to be added after fieldname `with colon` \neither `:a` for `asc` and `:d` for `desc`, other type is an object params with combination of keys:\n* `_where_` - string sql like statement as state above\n* `_limit_` + `_offset_` - number for pagination result set\n* `_pages_` - boolean to calculate how many pagination pages \n```js\nawait mitm.fn.sqlList()\n// (*sqlite sqlList*)\n// select * from `kv` []\n\nawait mitm.fn.sqlList('(hst like %o%) orderby hst id:d')\n// (*sqlite sqlList where:(hst LIKE ?) orderby:hst asc, id desc, [\"%o%\"]*)\n// select * from `kv` where (hst LIKE ?) order by `hst` asc, `id` desc [ '%o%' ]\n\nawait mitm.fn.sqlList('(hst like %o%) \u0026\u0026 id=20 orderby hst id:d')\n// (*sqlite sqlList where:(hst LIKE ?) AND id = ? orderby:hst asc, id desc, [\"%o%\",\"20\"]*)\n// select * from `kv` where (hst LIKE ?) AND id = ? order by `hst` asc, `id` desc [ '%o%', '20' ]\n\nawait mitm.fn.sqlList('(hst like %o%) \u0026\u0026 (id=20 || id=21) orderby hst id:d')\n// (*sqlite sqlList where:(hst LIKE ?) AND (id = ? OR id = ?) orderby:hst asc, id desc, [\"%o%\",\"20\",\"21\"]*)\n// select * from `kv` where (hst LIKE ?) AND (id = ? OR id = ?) order by `hst` asc, `id` desc [ '%o%', '20', '21' ]\n\nawait mitm.fn.sqlList({\n  _where_:'(hst like %o%) orderby dtu:d',\n  _limit_: 15,\n  _offset_: 0,\n  _pages_: true\n})\n// (*sqlite sqlList where:{\"_where_\":\"(hst like %o%) orderby dtu:d\",\"_limit_\":15,\"_offset_\":0,\"_pages_\":true}*)\n// select count(`id`) as `ttl` from `kv` where (hst LIKE ?) order by `dtu` desc [ '%o%' ]\n// select * from `kv` where `id` in (select `id` from `kv` where (hst LIKE ?) order by `dtu` desc limit ?) [ '%o%', 15 ]\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003emitm.fn.sqlDel - delete record(s)\u003c/summary\u003e\n\nparameters is required, the string parameters having same rules as `sqlList` excluding `orderby`\n```js\nawait mitm.fn.sqlDel('(hst like %o%) \u0026\u0026 app=WOW')\n// (*sqlite sqlDel where:(hst LIKE ?) AND app = ?, [\"%o%\",\"WOW\"]*)\n// delete from `kv` where (hst LIKE ?) AND app = ? [ '%o%', 'WOW' ]\n\nawait mitm.fn.sqlDel({_hold_:'id\u003e1 orderby hst:d', _limit_: 15})\n// (*sqlite sqlDel where:{\"_hold_\":\"id\u003e1 orderby hst:d\",\"_limit_\":15}*)\n// delete from `kv` where `id` in (select `id` from `kv` where id \u003e ? order by `hst` desc limit ? offset ?) [ '1', -1, 15 ]\n\nawait mitm.fn.sqlDel({id:1, _hold_:'id\u003e1 orderby hst:d', _limit_: 15})\n// (*sqlite sqlDel where:{\"id\":1,\"_hold_\":\"id\u003e1 orderby hst:d\",\"_limit_\":15}*)\n// delete from `kv` where `id` in (select `id` from `kv` where id \u003e ? order by `hst` desc limit ? offset ?) or (`id` = ?) [ '1', -1, 15, 1 ]\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003emitm.fn.sqlUpd - update record(s)\u003c/summary\u003e\n\nparameters is required, an object literal at minimum should be 2 field and the first field either `id` or `_where_` to indentify \nrecord that need to be updated.\n```js\nawait mitm.fn.sqlUpd({id:14, app: 'LOL2'})\n// (*sqlite sqlUpd set:{\"id\":14,\"app\":\"LOL2\"}*)\n// update `kv` set `app` = ?, `dtu` = CURRENT_TIMESTAMP where `id` = ? [ 'LOL2', 14 ]\n\nawait mitm.fn.sqlUpd({_upd_:'id\u003c10', app: 'below10'})\n// (*sqlite sqlUpd set:{\"_upd_\":\"id\u003c10\",\"app\":\"below10\"}*)\n// update `kv` set `app` = ?, `dtu` = CURRENT_TIMESTAMP where id \u003c ? [ 'below10', '10' ]\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003emitm.fn.sqlIns - add a new record\u003c/summary\u003e\n\nparameters is required, an object literal. it will serve two purpose: \n**first** `just insert a record` or **second** `to delete record(s) before insert` with `_hold_, _limit_, _del_` keys.\n```js\nawait mitm.fn.sqlIns({hst: 'demo2', grp: 'group2', typ: 'type2', name: 'name2', meta: 'meta2', data: 'data2'})\n// (*sqlite sqlIns set:{\"hst\":\"demo2\",\"grp\":\"group2\",\"typ\":\"type2\",\"name\":\"name2\",\"meta\":\"meta2\",\"data\":\"data2\"}*)\n// insert into `kv` (`data`, `dtc`, `dtu`, `grp`, `hst`, `meta`, `name`, `typ`) values (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?) [ 'data2', 'group2', 'demo2', 'meta2', 'name2', 'type2' ]\n\nawait mitm.fn.sqlIns({\n  _hold_:'id\u003e1 orderby hst:d',  \n  hst: 'demo3', grp: 'group3', typ: 'type3', name: 'name3', meta: 'meta3', data: 'data3'\n})\n// (*sqlite sqlIns set:{\"_hold_\":\"id\u003e1 orderby hst:d\",\"hst\":\"demo3\",\"grp\":\"group3\",\"typ\":\"type3\",\"name\":\"name3\",\"meta\":\"meta3\",\"data\":\"data3\"}*)\n// delete from `kv` where `id` in (select `id` from `kv` where id \u003e ? order by `hst` desc limit ? offset ?) [ '1', -1, 1 ]\n// insert into `kv` (`data`, `dtc`, `dtu`, `grp`, `hst`, `meta`, `name`, `typ`) values (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?) [ 'data3', 'group3', 'demo3', 'meta3', 'name3', 'type3' ]\n\nawait mitm.fn.sqlIns({\n  _hold_:'id\u003e1 orderby hst:d', _limit_: 15,  \n  hst: 'demo4', grp: 'group4', typ: 'type4', name: 'name4', meta: 'meta4', data: 'data4'\n})\n// (*sqlite sqlIns set:{\"_hold_\":\"id\u003e1 orderby hst:d\",\"_limit_\":15,\"hst\":\"demo4\",\"grp\":\"group4\",\"typ\":\"type4\",\"name\":\"name4\",\"meta\":\"meta4\",\"data\":\"data4\"}*)\n// delete from `kv` where `id` in (select `id` from `kv` where id \u003e ? order by `hst` desc limit ? offset ?) [ '1', -1, 15 ]\n// insert into `kv` (`data`, `dtc`, `dtu`, `grp`, `hst`, `meta`, `name`, `typ`) values (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?) [ 'data4', 'group4', 'demo4', 'meta4', 'name4', 'type4' ]\n\nawait mitm.fn.sqlIns({\n  _hold_:'id\u003e1 orderby hst:d', _limit_: 15, _del_:'id\u003c10', \n  hst: 'demo5', grp: 'group5', typ: 'type5', name: 'name5', meta: 'meta5', data: 'data5'\n})\n// (*sqlite sqlIns set:{\"_hold_\":\"id\u003e1 orderby hst:d\",\"_limit_\":15,\"_del_\":\"id\u003c10\",\"hst\":\"demo5\",\"grp\":\"group5\",\"typ\":\"type5\",\"name\":\"name5\",\"meta\":\"meta5\",\"data\":\"data5\"}*)\n// delete from `kv` where id \u003c ? [ '10' ]\n// delete from `kv` where `id` in (select `id` from `kv` where id \u003e ? order by `hst` desc limit ? offset ?) [ '1', -1, 15 ]\n// insert into `kv` (`data`, `dtc`, `dtu`, `grp`, `hst`, `meta`, `name`, `typ`) values (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?) [ 'data5', 'group5', 'demo5', 'meta5', 'name5', 'type5' ]\n```\n\u003c/details\u003e\n\nThere are three tables available: `kv(default)`, log \u0026 cache. `log \u0026 cache are preserved, not yet used`.\n\n# ws__send\nCreate socket custom command and later it can be use to update/manipulate object, it utilize ws_send function with built-in random keys to make command send to BE is unique\n```js\n// from browser CLI terminal \nws__send('ping', 'hi', d=\u003econsole.log(`result ${d}`)) // \u003e\u003e\u003e ws-message: `ping:G2kGPCYj{\"data\":\"pong hi!\"}`\n\n// example of socket custom command built in for the purpose of testing and validate the custom command\nwindow.mitm.wsrun.$ping = ({ data }) =\u003e { // it become: window.mitm.wsrun.$ping\n  return `pong ${data}!`\n},\n```\n# User Route\n[User-route](https://github.com/mitmplay/user-route) are available on this repo: https://github.com/mitmplay/user-route and it should be taken as an experiment to test `mitm-play` functionality. \n\nIf you think you have a nice routing want to share, you can create a PR to the [user-route](https://github.com/mitmplay/user-route) or add a `link` to your repo.  \n\n# Use Cases\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eReduce Internet usage\u003c/b\u003e\u003c/summary\u003e\n\nThere are several strategy to reduce internet usage, user commonly use different tools to achieve, either install new browser (ie: Brave) or install Add Blocker (ie: uBlock). Using mitm-play, developer can controll which need to be pass, blocked or cached. \n\n__Cache any reguest with content type: font, image, javascript, css__, if url contains cached busting, it may miss the cached, you can experiment by turning off `querystring` to `false`.\n```js\ncache: {\n  '.+': {\n    contentType: ['font','image','javascript','css'],\n    querystring: true,\n  }\n},\n```\n\n__Block/Mock unnecessary javascript with an empty result__, be careful to not block UX or content navigation.\n```js\nmock: {\n  'block/w/empty.js': '',\n  'some/url/with/adv.js': {\n    response(resp, reqs, match) {\n      const {body} = resp;\n      ...\n      return {body: '/* content is blocked! */'}\n    },\n  },\n},\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eSimplify Developer workflow\u003c/b\u003e\u003c/summary\u003e\n\nas developer sometime we need to get access to lots website in which some of the page need to be automated fill in and submit to the next page. \nWith `Macros` it can be done!\n\u003c/details\u003e\n\n# Early Stage\nExpect to have some `rule changed` as feature/fix code are incrementally committed.\n\n.\n\nGoodluck!,\n\u003e*-wh*.\n\n# Known Limitation\nIssue or Limitation on Playwright:\n\n* Route handler to support redirects [#3993](https://github.com/microsoft/playwright/issues/3993) / Disallow intercepting redirects [#2617](https://github.com/microsoft/playwright/pull/2617)\n  * or alternative intercept response is implemented [#1774](https://github.com/microsoft/playwright/issues/1774) ","funding_links":[],"categories":["chromium"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitmplay%2Fmitm-play","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitmplay%2Fmitm-play","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitmplay%2Fmitm-play/lists"}