🛠 refactor: new settings

This commit is contained in:
eshanized
2025-01-18 04:23:35 +05:30
parent 5744e5e604
commit af081111e6
13699 changed files with 650531 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
{
"checksum": "b81ce88b1c1d48e0a21a7b818bddc01f",
"roots": {
"bookmark_bar": {
"children": [ ],
"date_added": "13381355416679344",
"date_last_used": "0",
"date_modified": "0",
"guid": "0bc5d13f-2cba-5d74-951f-3f233fe6c908",
"id": "1",
"name": "Bookmarks",
"type": "folder"
},
"other": {
"children": [ {
"children": [ {
"children": [ {
"date_added": "13381525130302734",
"date_last_used": "0",
"guid": "1bda7ae6-8907-4247-8577-436e23c18560",
"id": "9",
"meta_info": {
"power_bookmark_meta": ""
},
"name": "SNIGDHA OS · GitLab",
"type": "url",
"url": "https://gitlab.com/SnigdhaOS"
} ],
"date_added": "13381356153748966",
"date_last_used": "0",
"date_modified": "13381525130302734",
"guid": "f4e43918-bd39-4cd0-887e-ba1fa59e51f5",
"id": "6",
"name": "Bookmarks",
"type": "folder"
} ],
"date_added": "13381356134574670",
"date_last_used": "0",
"date_modified": "13381356153749043",
"guid": "33d20769-71b4-4df8-9e8a-a163c23a8106",
"id": "5",
"name": "Homey",
"type": "folder"
} ],
"date_added": "13381355416679346",
"date_last_used": "0",
"date_modified": "13381525108632025",
"guid": "82b081ec-3dd3-529c-8475-ab6c344590dd",
"id": "2",
"name": "Other bookmarks",
"type": "folder"
},
"synced": {
"children": [ ],
"date_added": "13381355416679348",
"date_last_used": "0",
"date_modified": "0",
"guid": "4cf2e351-0e85-532b-bb37-df045d8f8d0f",
"id": "3",
"name": "Mobile bookmarks",
"type": "folder"
}
},
"version": 1
}

View File

@@ -0,0 +1,65 @@
{
"checksum": "b81ce88b1c1d48e0a21a7b818bddc01f",
"roots": {
"bookmark_bar": {
"children": [ ],
"date_added": "13381355416679344",
"date_last_used": "0",
"date_modified": "0",
"guid": "0bc5d13f-2cba-5d74-951f-3f233fe6c908",
"id": "1",
"name": "Bookmarks",
"type": "folder"
},
"other": {
"children": [ {
"children": [ {
"children": [ {
"date_added": "13381525130302734",
"date_last_used": "0",
"guid": "1bda7ae6-8907-4247-8577-436e23c18560",
"id": "9",
"meta_info": {
"power_bookmark_meta": ""
},
"name": "SNIGDHA OS · GitLab",
"type": "url",
"url": "https://gitlab.com/SnigdhaOS"
} ],
"date_added": "13381356153748966",
"date_last_used": "0",
"date_modified": "13381525130302734",
"guid": "f4e43918-bd39-4cd0-887e-ba1fa59e51f5",
"id": "6",
"name": "Bookmarks",
"type": "folder"
} ],
"date_added": "13381356134574670",
"date_last_used": "0",
"date_modified": "13381356153749043",
"guid": "33d20769-71b4-4df8-9e8a-a163c23a8106",
"id": "5",
"name": "Homey",
"type": "folder"
} ],
"date_added": "13381355416679346",
"date_last_used": "0",
"date_modified": "13381525108632025",
"guid": "82b081ec-3dd3-529c-8475-ab6c344590dd",
"id": "2",
"name": "Other bookmarks",
"type": "folder"
},
"synced": {
"children": [ ],
"date_added": "13381355416679348",
"date_last_used": "0",
"date_modified": "0",
"guid": "4cf2e351-0e85-532b-bb37-df045d8f8d0f",
"id": "3",
"name": "Mobile bookmarks",
"type": "folder"
}
},
"version": 1
}

View File

@@ -0,0 +1 @@
MANIFEST-000001

View File

@@ -0,0 +1,3 @@
2025/01/18-04:16:30.291 d8f Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/BraveWallet/Brave Wallet Storage/MANIFEST-000001
2025/01/18-04:16:30.291 d8f Recovering log #3
2025/01/18-04:16:30.292 d8f Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/BraveWallet/Brave Wallet Storage/000003.log

View File

@@ -0,0 +1,3 @@
2025/01/18-01:45:23.067 1450 Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/BraveWallet/Brave Wallet Storage/MANIFEST-000001
2025/01/18-01:45:23.067 1450 Recovering log #3
2025/01/18-01:45:23.067 1450 Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/BraveWallet/Brave Wallet Storage/000003.log

Binary file not shown.

View File

@@ -0,0 +1 @@
[{"action":{"requestHeaders":[{"header":"Sec-Fetch-Dest","operation":"set","value":"document"}],"responseHeaders":[{"header":"X-Frame-Options","operation":"remove"},{"header":"report-to","operation":"remove"},{"header":"content-security-policy-report-only","operation":"remove"},{"header":"content-security-policy","operation":"remove"}],"type":"modifyHeaders"},"condition":{"initiatorDomains":["lllnjdmfnfjifcfpppjmcnanpokikcpl"],"requestDomains":["translate.google.com","drive.google.com","keep.google.com","www.google.com","news.google.com","photos.google.com","www.youtube.com","music.youtube.com","chatgpt.com","gemini.google.com","web.whatsapp.com","www.tiktok.com","www.instagram.com","www.youtube.com"],"resourceTypes":["sub_frame"]},"id":1,"priority":1}]

View File

@@ -0,0 +1 @@
MANIFEST-000001

View File

@@ -0,0 +1,3 @@
2025/01/18-03:21:20.699 144f Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Rules/MANIFEST-000001
2025/01/18-03:21:20.699 144f Recovering log #3
2025/01/18-03:21:20.699 144f Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Rules/000003.log

View File

@@ -0,0 +1,3 @@
2025/01/17-21:29:27.133 1756d Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Rules/MANIFEST-000001
2025/01/17-21:29:27.133 1756d Recovering log #3
2025/01/17-21:29:27.133 1756d Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Rules/000003.log

View File

@@ -0,0 +1 @@
MANIFEST-000001

View File

@@ -0,0 +1,3 @@
2025/01/18-03:21:20.700 144f Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Scripts/MANIFEST-000001
2025/01/18-03:21:20.701 144f Recovering log #3
2025/01/18-03:21:20.701 144f Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Scripts/000003.log

View File

@@ -0,0 +1,3 @@
2025/01/17-21:29:27.134 1756d Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Scripts/MANIFEST-000001
2025/01/17-21:29:27.134 1756d Recovering log #3
2025/01/17-21:29:27.134 1756d Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension Scripts/000003.log

View File

@@ -0,0 +1 @@
MANIFEST-000001

View File

@@ -0,0 +1,3 @@
2025/01/18-04:16:30.888 d8f Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension State/MANIFEST-000001
2025/01/18-04:16:30.889 d8f Recovering log #3
2025/01/18-04:16:30.893 d8f Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension State/000003.log

View File

@@ -0,0 +1,3 @@
2025/01/18-01:45:23.522 145d Reusing MANIFEST /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension State/MANIFEST-000001
2025/01/18-01:45:23.522 145d Recovering log #3
2025/01/18-01:45:23.523 145d Reusing old log /home/whoami/.config/BraveSoftware/Brave-Browser/Default/Extension State/000003.log

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2024 Varun Malhotra
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

View File

@@ -0,0 +1 @@
{"file_hashes":[{"block_hashes":["8B5pOxArlew8GbNhOJY51SeiyVpeu8hp8zeoLTI49oY="],"block_size":4096,"path":"LICENSE"},{"block_hashes":["MQ55lJjIYVsPZOu+QP5wkcx1xb7uUb/a4s3Wf4Dg/WE="],"block_size":4096,"path":"background.js"},{"block_hashes":["B/eYE/Pg8RdxnSRR1OXXRuIadNgwc4kNhjEaiZ5KQMs=","sIx/UqeXGp+0cRKBpwfClsrePW0PyqZdXbzz7gkdPPU="],"block_size":4096,"path":"options.html"},{"block_hashes":["9fZgwvhTnEC/7iXOQIt2z6ufK2gfQM0uxgQOnjdAicQ="],"block_size":4096,"path":"options.js"},{"block_hashes":["4GLlkLUl+zjPHc+Dcz2fzDMEWoJHgKo3+ulzAV668I8="],"block_size":4096,"path":"popup.html"},{"block_hashes":["1AnZuM/e6mT5VfFWNrkwvQTR8xFjSSnQDQgeazY21xE="],"block_size":4096,"path":"popup.js"},{"block_hashes":["hhpqJ7s8fRLAKwcMpXJRw0pDv+UglocwFfrwgDBrz5I=","pzraygC0hBVggDglKAeXWp50vRGa9KckOdfgsAnI8LI=","libEFfYSaMIVY1uxgBbsQdVlG793qEMHs0oxrb/pvHE=","LeDhH/E7+nWHTEYS80EVcFkeO0SXJMeU+S+MXKs1XBI=","F9nxsKcFPcPL9xt887WP5JRjbWY9XTc1dAXXWHNaDqM="],"block_size":4096,"path":"src/inject.js"},{"block_hashes":["nZ82vLatkG5Ga7eR/sMZwdLjU+eDICKAgfePRKGnXzI="],"block_size":4096,"path":"src/inject.js.LICENSE.txt"},{"block_hashes":["MeaAS/DX5lR1oE88icEa6Jn2NgbhLLQSoUjM9iSl2O0=","pE0F51J+XZLGrjkXcndMqoOaZ8nAn7PpM5Fv4UmJLFg=","ynin762DA4i9SbfbFQqNjL3UvkE19nOiwR3pNX81s8M=","YzryNs2Cpw4h1X4+bc4pdoK44ouUgFuFY6Zb9cPXz/U=","A4k14VTNA5nvKt7Qa5UcDPkE728opDplDC4C6uf1DCU=","JOeoezCTxvjHqAXHxp1zCeSPA7G/j39HeswNRutdqow=","UD3Y6ntzimvgMOV7LbZtfwq6YM5mkzcgLGqajObSTQk=","ZlI5dkQwtj9VIcZRqCXrCj0R2gu4O7sOLFSVBi6rrTE=","g5sAePcm+4tEBR1MwW8FcRV+KLQl5MxkC49Ut9AcbvY=","1hnsKNBXreoY9RVnt777A5r1Umz24H/DSdIlK2RmbxI=","Jhui/TrD5zO9W2hJhntJeK7rhiHph184LW77VHlJxNE=","9NjhAdIQkOtlyI6SlDKs2JdYzIl3IW+RFdgtPr0qLd4=","W86xmdnE3skM8XSyzYLlk+UQc51DBiBunG3K5Bn4rOI=","Xb2QYGbTN9ZmibUZtj+Lr9N6l0fSsaB/82APOUq8mrs=","erkXR/1bbLndLb5S1ozQybpfQxBxqtXD2tvTxKbv8+8=","FMJzqxoyrtTqbnJEQ9GXOkpnvxVe+HhWNiPZAgbLIyY=","RmgS29twYJyq9Z2FGA5bytYPjfbSRfULKWeREw33NDA=","vHdGBotlFkOOhDE4eiDPf3h8geNAJpHutDiU+nrrI6A="],"block_size":4096,"path":"src/inject.js.map"}],"version":2}

View File

@@ -0,0 +1 @@
[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJMSUNFTlNFIiwicm9vdF9oYXNoIjoiOEI1cE94QXJsZXc4R2JOaE9KWTUxU2VpeVZwZXU4aHA4emVvTFRJNDlvWSJ9LHsicGF0aCI6ImJhY2tncm91bmQuanMiLCJyb290X2hhc2giOiJNUTU1bEpqSVlWc1BaT3UtUVA1d2tjeDF4Yjd1VWJfYTRzM1dmNERnX1dFIn0seyJwYXRoIjoiaWNvbnMvZW5oYW5jZWQtZ2l0aHViMTI4LnBuZyIsInJvb3RfaGFzaCI6InBEaFZsdjVSSEdhQlh5WXkyZVZQRFZ5aEJIWjRnN3JGc3FLWDd2eVg2QkEifSx7InBhdGgiOiJpY29ucy9lbmhhbmNlZC1naXRodWIxNi5wbmciLCJyb290X2hhc2giOiJMVWdvd2l3em5rOE5xRGlFR25kaU92MVhoOWU2NmstdFQ1LURmT1hXZzNnIn0seyJwYXRoIjoiaWNvbnMvZW5oYW5jZWQtZ2l0aHViNDgucG5nIiwicm9vdF9oYXNoIjoiaUF5YzJnQ1BDN0RoelBzelFhUVFkbkxCaV9aYXVENzRXVXFYLTg5TU5qVSJ9LHsicGF0aCI6Im1hbmlmZXN0Lmpzb24iLCJyb290X2hhc2giOiJLSVN0SmhOUE5yLWxvZWYwel9yc1JOZ1RiSUhaeElIcGZUY2l3UjlFTHZ3In0seyJwYXRoIjoib3B0aW9ucy5odG1sIiwicm9vdF9oYXNoIjoiWFFwLWNFMW1tWC0td282akd4MmNOWEl0WHFnMTFqSkxrSHVkeUtGNjRuWSJ9LHsicGF0aCI6Im9wdGlvbnMuanMiLCJyb290X2hhc2giOiI5Zlpnd3ZoVG5FQ183aVhPUUl0Mno2dWZLMmdmUU0wdXhnUU9uamRBaWNRIn0seyJwYXRoIjoicG9wdXAuaHRtbCIsInJvb3RfaGFzaCI6IjRHTGxrTFVsLXpqUEhjLURjejJmekRNRVdvSkhnS28zLXVsekFWNjY4STgifSx7InBhdGgiOiJwb3B1cC5qcyIsInJvb3RfaGFzaCI6IjFBblp1TV9lNm1UNVZmRldOcmt3dlFUUjh4RmpTU25RRFFnZWF6WTIxeEUifSx7InBhdGgiOiJzcmMvaW5qZWN0LmpzIiwicm9vdF9oYXNoIjoiRWFHMExUTEo3QXBIajFSQzIwMkE5YWxVWUFHVzlpR0FzZG96a3RQWDhUMCJ9LHsicGF0aCI6InNyYy9pbmplY3QuanMuTElDRU5TRS50eHQiLCJyb290X2hhc2giOiJuWjgydkxhdGtHNUdhN2VSX3NNWndkTGpVLWVESUNLQWdmZVBSS0duWHpJIn0seyJwYXRoIjoic3JjL2luamVjdC5qcy5tYXAiLCJyb290X2hhc2giOiJsc3ExU0JkOGlRX01YRGNiaUlabnV4UklabTlzcmJMUEh0LUFtMjF2aEFNIn1dLCJmb3JtYXQiOiJ0cmVlaGFzaCIsImhhc2hfYmxvY2tfc2l6ZSI6NDA5Nn1dLCJpdGVtX2lkIjoiYW5saWtjbmJnZGVpZHBhY2RiZGxqbmFiY2xoYWhobWQiLCJpdGVtX3ZlcnNpb24iOiI2LjAuMCIsInByb3RvY29sX3ZlcnNpb24iOjF9","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"ZeJBDQYdFUEIcDPgkj5ho_iZ3AtDriNDrYJ4gZ6GBS8qI8Zp_c_vLQGCUaQV5feJ7LYZol1e8O4jgYOwJNehrxGPeNQCQRTnNMTD2S98YwEnXGhsDZm8v0-f8Kh7nK7O1DS9Bf4rq0UKW4MLE1iBK0BE_iD54RoaL1iqOUi7HFMC4QL1OKn8d3ltiSh4QVfMzlMa3BX-CR-4LpGTiA1gPeKOymahxneC_06zN66blWWaSRn1h5G0GnxJZc6HFuqziru-WlAShGfDDCzmQvEx6yJOl5l35BcNgQ9ei7pCI7_YkKbMg5Ay5YdcPpKF9pYPJqU9AzmLuwWsvkS1ce30_A"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"WGxJtFjHZdpv3NUp5bhOaMmq5AxYqr_ZIiDNcxGnzI-7MT9EHd0lubcbhvbb588TzPAGhnmTzK_qzz3ET0_HqQh7cjyt2xNRGCD7m7_GueCLhbxiK3S7fEdsjg449r1Cz7ma7Auj2wUXcFqvQNjQklK3R8QOt0-PaERn1IcI6XfHoWwRK_yyKAp95Rq2MIFcqrjE_H213ZPmWIjIh6wgHjqJQBU_pw_lSuc7VHJMec2oD8ztuE304yQTtFQ9BjpWiKhJR5tbgVQwXNcG6RBm9gN84GGemvyZfYoosJRjgZGIrq5YeW71j1jMUSv7M5z6FgtddsuWgVybb2OlODPLYA"}]}}]

View File

@@ -0,0 +1 @@
const MessageType={PAGE_RENDERED:"pageRendered"};let tabId,currentUrl="";chrome.webRequest.onCompleted.addListener((function(e){const t=new URL(e.url);currentUrl&&currentUrl.indexOf(t.pathname)>-1&&tabId&&chrome.tabs.sendMessage(tabId,{type:MessageType.PAGE_RENDERED})}),{urls:["*://*.github.com/*"]}),chrome.webNavigation.onHistoryStateUpdated.addListener((e=>{tabId=e.tabId,currentUrl=e.url}),{url:[{hostSuffix:"github.com"}]});

View File

@@ -0,0 +1,37 @@
{
"action": {
"default_icon": "icons/enhanced-github48.png",
"default_popup": "popup.html",
"default_title": "Enhanced GitHub"
},
"author": "Varun Malhotra",
"background": {
"service_worker": "background.js"
},
"content_scripts": [ {
"js": [ "src/inject.js" ],
"matches": [ "*://*.github.com/*" ]
} ],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
},
"description": "Display repo size, size of each file, download link and option to copy file contents",
"homepage_url": "https://github.com/softvar/enhanced-github",
"host_permissions": [ "*://*.github.com/*" ],
"icons": {
"128": "icons/enhanced-github128.png",
"16": "icons/enhanced-github16.png",
"48": "icons/enhanced-github48.png"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjj6TV2KuFrCZYgYvwDyfEUrtRytpbw/DqAFRPXXwdGnwTBoPqtU3m3Lrfjp1OLrx0Ky6/8Ay6f4vcc5YLwtkGq4v1E1c6snv3ButD+FlL3WdSTIuNVAkZ7zgjStlrQizOZVLN/LzMiav6YwGehSi0I6q1sCGEXfcWo2MQep/kNSYuc1TptU615W8HgQY1gcPEQ2ocEq10gel/I07dvbCpLoei5GHjj3TOc6+guu7aMjknsnAc6ZkP4u8KWTUr9BrLJVxBwwhhAA1cXyHiIoKCBIgUsoO5ZQL4JlrS1gH/b5+hZv9jkaNZG0Q+s4rQ8tD7xAm7F7WOY/nbA1rtPPmiwIDAQAB",
"manifest_version": 3,
"name": "Enhanced GitHub",
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
"permissions": [ "storage", "webRequest", "webNavigation" ],
"short_name": "Enhanced GitHub",
"update_url": "https://clients2.google.com/service/update2/crx",
"version": "6.0.0"
}

View File

@@ -0,0 +1,170 @@
<!DOCTYPE html>
<html>
<head>
<title>Enhanced GitHub Options</title>
<meta charset="utf-8">
<style type="text/css">
body {
font-size: 15px;
}
.background--grey {
background: #f5f5f5;
}
.padding--10 {
padding: 10px;
}
.push--top {
margin-top: 10px;
}
.text--center {
text-align: center;
}
.vertical-center {
vertical-align: middle;
}
.inline-block {
display: inline-block;
}
.hidden {
display: none;
}
.float--right {
float: right;
}
.success-msg {
color: #3fb594;
}
.warning-msg {
color: #de973e;
}
.btn {
background: #3fb594;
color: #fff !important;
border: none;
border-radius: 2px;
color: #000;
position: relative;
height: 36px;
margin: 0;
min-width: 100%;
padding: 0 16px;
display: inline-block;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 14px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0;
overflow: hidden;
will-change: box-shadow;
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
cursor: pointer;
text-decoration: none;
text-align: center;
line-height: 36px;
vertical-align: middle;
}
.btn:hover {
background: #3fb594;
color: #fff !important;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
}
.btn.disabled {
background: #fefefe;
pointer-events: none;
}
.ph-link {
color: #da552f;
}
</style>
</head>
<body>
<div class="inline-block float--right hidden" id="status" style="margin-top: 15px;">
<svg
fill="#3fb594"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
class="vertical-center"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
<span id="status--text" class="success-msg vertical-center"></span>
</div>
<h3 class="inline-block">Options</h3>
<div class="background--grey push--top padding--10">
<div style="padding-bottom: 10px;">Enter GitHub Access Token</div>
<input
type="text"
name=""
placeholder="GitHub Access Token"
id="x-github-token"
class="push--top"
style="width: 98%;"
/>
<div class="push--top hidden" id="validation-block">
<svg
fill="#de973e"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
class="vertical-center"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
</svg>
<span id="validation-warning" class="warning-msg vertical-center push--top"></span>
</div>
<button class="btn" id="save-btn" style="margin-top: 20px;">Save</button>
</div>
<hr />
<div class="background--grey padding--10 text--center">
<div style="padding: 15px 10px;">
<!-- Place this tag where you want the button to render. -->
<iframe
src="https://ghbtns.com/github-btn.html?user=softvar&repo=enhanced-github&type=star&count=true&size=large"
frameborder="0"
scrolling="0"
width="160px"
height="38px"
></iframe>
<iframe
src="https://platform.twitter.com/widgets/tweet_button.html?size=l&url=http://github.com/softvar/enhanced-github&via=s0ftvar&text=Display size of each file, download link and option to copy file contents&hashtags=extension,github"
title="Twitter Tweet Button"
style="width: 76px;height: 37px;border: 0;overflow: hidden;display: inline-block;"
>
</iframe>
</div>
<div>
<a class="ph-link" href="https://www.producthunt.com/tech/enhanced-github" target="_blank">
Featured On ProductHunt
</a>
</div>
</div>
<script type="text/javascript" src="options.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
function validateUserToken(e){var t;return"string"!=typeof e?t="Access Token should be a String eg: 17c1a8d5b399d66b6212382d98d4c67a94d58955":e.length<30&&(t="Access Token length appears wrong!"),document.getElementById("validation-block").style.display=t?"inline-block":"none",t}function saveOptions(){document.getElementById("save-btn").setAttribute("disabled","disabled"),document.getElementById("save-btn").style.background="#DEDEDE";var e=document.getElementById("x-github-token").value;chrome.storage.sync.set({"x-github-token":e},(function(){var t=document.getElementById("status--text"),n=document.getElementById("validation-warning"),o=validateUserToken(e);o||(t.textContent="Options saved!!",document.getElementById("status").style.display="inline-block"),n.textContent=o,setTimeout((function(){t.textContent="",document.getElementById("status").style.display="none",document.getElementById("save-btn").removeAttribute("disabled"),document.getElementById("save-btn").style.background="#3fb594"}),1500)}))}function restoreOptions(){var e;chrome.storage.sync.get({"x-github-token":""},(function(t){e=t["x-github-token"],document.getElementById("x-github-token").value=e,document.getElementById("validation-warning").textContent=validateUserToken(e),document.getElementById("save-btn").style.background="#3fb594"}))}document.addEventListener("DOMContentLoaded",restoreOptions),document.getElementById("save-btn").addEventListener("click",saveOptions);

View File

@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Enhanced GitHub | Home</title>
<style type="text/css">
h4 {
color: #666;
}
.background-grey {
background: #f5f5f5;
}
.border--thin-grey {
border: 1px solid #e2e1e1;
}
.padding--10 {
padding: 10px;
}
.ph-link {
color: #da552f;
}
#settings-btn {
cursor: pointer;
}
#settings-btn svg:hover {
fill: #444 !important;
}
</style>
</head>
<body style="width: 500px;">
<div class="background-grey padding--10">
<div
id="settings-btn"
style="text-align: right; float: right; margin-top: 15px;"
title="Options: Add Access Token"
>
<svg fill="#989898" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"
/>
</svg>
<div>Add GitHub Token</div>
</div>
<img src="icons/enhanced-github128.png" width="100" height="100" style="vertical-align: middle;" />
<h2 style="display: inline-block; margin-left: 10px;"><span style="color:red;">Enhanced</span> GitHub</h2>
</div>
<h4>Works only on <i>github.com/*</i></h4>
<div class="background-grey border--thin-grey" style=" padding: 10px 0;">
<ol>
<li>Automatically displays each file size in every active branch (not applicable for folder / symlink).</li>
<li>Show download link for each individual file (not applicable for folder / symlink).</li>
<li>Copy file's contents directly to Clipboard (just won't work for markdown files).</li>
<li>Download file content.</li>
</ol>
</div>
<h4>More features coming soon...Stay tuned!</h4>
<div class="background-grey border--thin-grey" style="padding: 20px; text-align:center;">
<iframe
src="https://ghbtns.com/github-btn.html?user=softvar&repo=enhanced-github&type=star&count=true&size=large"
frameborder="0"
scrolling="0"
width="160px"
height="30px"
></iframe>
<iframe
src="https://platform.twitter.com/widgets/tweet_button.html?size=l&url=http://github.com/softvar/enhanced-github&via=s0ftvar&text=Display size of each file, download link and option to copy file contents&hashtags=extension,github"
width="80"
height="28"
title="Twitter Tweet Button"
style="width: 76px; border: 0; overflow: hidden; display: inline-block;"
>
</iframe>
<div style="margin-top: 10px;">
<a class="ph-link" href="https://www.producthunt.com/tech/enhanced-github" target="_blank">
Featured On ProductHunt
</a>
</div>
</div>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
document.getElementById("settings-btn").addEventListener("click",(function(){chrome.runtime.openOptionsPage()}));

View File

@@ -0,0 +1,24 @@
/*!
* clipboard.js v2.0.11
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/
/*!
* enhanced-github
* https://github.com/softvar/enhanced-github
*
* Licensed MIT (c) Varun Malhotra
*/
/*!
* enhanced-github - v6.0.0
*
* URL - https://github.com/softvar/ehanced-github
*
* MIT License, Copyright Varun Malhotra
*
* Dependencies used -
* 1. clipboard - ^2.0.11
*/

View File

@@ -0,0 +1 @@
[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJiYWNrZ3JvdW5kL29mZnNjcmVlbi5odG1sIiwicm9vdF9oYXNoIjoiY1pwRGtMaUZnYW9mNHo2Zk9lOGFGZlBiUHBSSWRiVTFrY0lRQ0lzQW1fSSJ9LHsicGF0aCI6ImJhY2tncm91bmQvb2Zmc2NyZWVuLmpzIiwicm9vdF9oYXNoIjoiLU1lOUMtZ09YZnNDWk1BUXRnNUJQZGZPdHJMOTZjNDNTYmZjejN2SjFkMCJ9LHsicGF0aCI6ImJhY2tncm91bmQvcmVjb3JkZXItc2VydmljZS13b3JrZXIuanMiLCJyb290X2hhc2giOiJtUVVTUnIwV1MzcW40c1BIb0RuaHJnZUFRdWpvdUdhaUl1aHJBeUtJeVlRIn0seyJwYXRoIjoiY2xhc3Nlcy9SZWNvcmRlckNvbnRyb2wuanMiLCJyb290X2hhc2giOiJlNW1vSXlDdG0wMmdFTnMwYkR0M0FoR2M3T1Bwc3ZRZUNwdUFFMFZfTThZIn0seyJwYXRoIjoiY29udGVudC1zY3JpcHRzL2NsYXNzZXMvUmVjb3JkZXIuanMiLCJyb290X2hhc2giOiJ0enJIZzZpczRfUHdfNnVPQTJoa3NHb19aMFlvSFJMckFXSnJVcVBhUWxJIn0seyJwYXRoIjoiaW1hZ2VzL1dlYkFwcC1SZWNvcmRlci0xMjgucG5nIiwicm9vdF9oYXNoIjoiaG9jaUxkTkhlbkYtSzAxQ216OWpmcDhhX1gzQWQwY0pYWlRuWFRTaUlEQSJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItMTYucG5nIiwicm9vdF9oYXNoIjoiRW1Eak9HRENQLVRiTUpSZ2t3bUxtSWdSd3d5bnpPMmd0ajZwcENmZjFBRSJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItMTkucG5nIiwicm9vdF9oYXNoIjoidFZNazhNalpFZmxEOExJZGV6UEdSYk5kMDdWYkEtaElYZWo1bjFGdklVZyJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItMzIucG5nIiwicm9vdF9oYXNoIjoiMER1WmRWUEt6c0xlb1ZZS1ZKRU55YWxKTEZCaU5IZGYySElWZFZWcHY5SSJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItMzgucG5nIiwicm9vdF9oYXNoIjoiRVNzMVRmdmY1ME9NaHhQTzZVRnF0NTZxS0JLMnItaVRoeTZNNjlBTXRhcyJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItNDgucG5nIiwicm9vdF9oYXNoIjoia3hLTWxwWHVOOHJrNE84enNfUWVQQ0taVHdXVlVxNHJrSExaUzh1U0lPUSJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItcmVjb3JkaW5nLWljb24ucG5nIiwicm9vdF9oYXNoIjoiM0NwclJYTXdDSXNPRlkwbkllWjA4YkdDWjhZVDhQM05KV1N6NnhKcllhWSJ9LHsicGF0aCI6ImltYWdlcy9XZWJBcHAtUmVjb3JkZXItc3RvcC1pY29uLnBuZyIsInJvb3RfaGFzaCI6IjNhMEhOTGM5NjNzNVJpNURnSmFGRzZaU05hTTdxRk5aOW1wMjdIZFRXcUUifSx7InBhdGgiOiJpbWFnZXMvY2xvc2Uuc3ZnIiwicm9vdF9oYXNoIjoiXzFPX2RuRnRBQzVlMHZoUzdTWlFEOWotYjdZNjdfbXNyaThqSjllaGtpQSJ9LHsicGF0aCI6ImltYWdlcy9jb3B5LXRvLWNsaXBib2FyZC5zdmciLCJyb290X2hhc2giOiJ1Z09odF9LYXlVbnIzT1BxZzNqcmRSaWVOcmIyRVBXR0N1UVF6bWsxbVBVIn0seyJwYXRoIjoiaW1hZ2VzL2luY29nbml0by1hbmltYXRpb24uZ2lmIiwicm9vdF9oYXNoIjoiTi1tdnpSbk5sMDF5UWRrcWJkeTdwdlhhenlBVVZrM05felBlb3NLcjliOCJ9LHsicGF0aCI6ImltYWdlcy9pbmNvZ25pdG8tc2NyZWVuc2hvdC5wbmciLCJyb290X2hhc2giOiJHcTYwSDZVTW45dEwyaEt5ZEk1RFhDTjhBX2toXzF4TUZsamh2Qk1UMkhRIn0seyJwYXRoIjoiaW1hZ2VzL3ZpZGVvLWNhbWVyYS5zdmciLCJyb290X2hhc2giOiJYVFlhLVpFZ3ZueXV5UnBXVGtlWXVFUmtZWTFYWW1mTV9ENEhXOFJGVTV3In0seyJwYXRoIjoibWFuaWZlc3QuanNvbiIsInJvb3RfaGFzaCI6IjhTbmFhbUVJSjJRTlVMMEpVN0xrRGljSHcxZXROLWtDdWxyMXBtV0UyM2MifSx7InBhdGgiOiJwb3B1cC9jb21wbGV0ZS5odG1sIiwicm9vdF9oYXNoIjoiSEY2elRYVWJxQVEwX05Wd3VNcENrMHNMZU1DMmhLSnRVbVgzUWFheUZhdyJ9LHsicGF0aCI6InBvcHVwL2hlbHAuaHRtbCIsInJvb3RfaGFzaCI6IjVtZ21rbldlTVBKeU9EYlNmZ3dwT09SRHFHYlR3OERTZEFxZ0s3c3dNRFEifSx7InBhdGgiOiJwb3B1cC9qcy9jb21wbGV0ZS5qcyIsInJvb3RfaGFzaCI6IlRQdzZQTVBIejlPdTEtTWlMZjBDeVZVQ0RncUVmUEFHd2V3Y1ZzaERrZjgifSx7InBhdGgiOiJwb3B1cC9qcy9oZWxwLmpzIiwicm9vdF9oYXNoIjoiMWJYa2prWEJhNWFUNVJ5TXE4RHdOT3YyWmJYQzEzVzFTZEl6bk1wb2ZfVSJ9LHsicGF0aCI6InBvcHVwL2pzL3BvcHVwLmpzIiwicm9vdF9oYXNoIjoiLVlqX0ZjNWczTWs4OHBRcTROckx2ckZfeUtwbjZwZWQxakZOQUhWNW9nMCJ9LHsicGF0aCI6InBvcHVwL2pzL3NoYXJlZC5qcyIsInJvb3RfaGFzaCI6ImpIakk2U2VIVWY2Uy02M0JsaEh1WFhhU1VPX21hdl9qVW4yMXZWNUdSXzQifSx7InBhdGgiOiJwb3B1cC9wb3B1cC5odG1sIiwicm9vdF9oYXNoIjoiTzJjMnFhRVJEd2wzWkZ1YjZZQzlaN2xyeUpuSmJtODZ4anZ1VTllTTBWZyJ9LHsicGF0aCI6InBvcHVwL3N0eWxlcy9zdHlsZXMuY3NzIiwicm9vdF9oYXNoIjoieS05ZzhfamNuS0RQQTctSEpVTWY5U2VORUhSNkN3dlZHSnFyNlJZb1RzQSJ9XSwiZm9ybWF0IjoidHJlZWhhc2giLCJoYXNoX2Jsb2NrX3NpemUiOjQwOTZ9XSwiaXRlbV9pZCI6ImFucGFwamNsYmppY2FjYWtlb2dnZ2hmbGRwcGJrZXBnIiwiaXRlbV92ZXJzaW9uIjoiMi4wLjEzIiwicHJvdG9jb2xfdmVyc2lvbiI6MX0","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"Dxf9maOPul3xeznNcIokqdOGlDnqzi0CO5iOMdkFKeol3qb01yf4POo5vAtszfCKmIXImXJ6rUPHtm5N6DPUcJc9YQ6Gzb3uaqOKV0A5LS1DFZA-YJBD_AEOq76OkGQXwHv28C9_lWw4Xrxr7RvNd-tL_oCwKIkHfomQuHXlxKokWeXUr7lFkl7xVPDTcPszFUvsKgQGKCHvI3DfVDUwSBBBjexNtSebyP2iQNri7OaNF1Dxh5pqzV4ywVP3AdApQ-Lq0Je-iQArmqwQAhp4y6qLMAaGqRO9UPB_R8BVZHcU05mXB3r-hzbeaZYAkKTJCt5AQOvt9CwdH9V-H3nUdA"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"GLQmifiUYkI68pAwfnbo0dW5aN0qNVomc2ojo8YYQn-GhYewCSL0wj0B2RuWsXTcJjx2x5NCPZtOsgnx6MFN1pxSWjX1GR5ySkwHvNarbW4hBbrXP0GyRu_uWiEZtGfhjecZZibh7sAfbdPK36u0-6xpO0QI3q2ZtpJWFzqSBQSIk2nkXxX2fG1GNr_EV3qKWwl2voXFds0zSEh4pahGkfuuJO_ydBdgSlgjW3IV0CMaQyKAUC53MWtfXsVgOthbcPQYxgm6UIRc5Ib-78DuhD7KQrLKwYSyCCSCPCZ_t6aWOqJ-feLi_R25cuKzk3IwGy--AhaOWjcd7kUz_3APWA"}]}}]

View File

@@ -0,0 +1,3 @@
<!doctype html>
<textarea id="text"></textarea>
<script src="offscreen.js"></script>

View File

@@ -0,0 +1,74 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Once the message has been posted from the service worker, checks are made to
// confirm the message type and target before proceeding. This is so that the
// module can easily be adapted into existing workflows where secondary uses for
// the document (or alternate offscreen documents) might be implemented.
// Registering this listener when the script is first executed ensures that the
// offscreen document will be able to receive messages when the promise returned
// by `offscreen.createDocument()` resolves.
chrome.runtime.onMessage.addListener(handleMessages);
// This function performs basic filtering and error checking on messages before
// dispatching the
// message to a more specific message handler.
async function handleMessages(message) {
// Return early if this message isn't meant for the offscreen document.
if (message.target !== 'offscreen-doc') {
return;
}
// Dispatch the message to an appropriate handler.
switch (message.type) {
case 'copy-data-to-clipboard':
handleClipboardWrite(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
}
}
// We use a <textarea> element for two main reasons:
// 1. preserve the formatting of multiline text,
// 2. select the node's content using this element's `.select()` method.
const textEl = document.querySelector('#text');
// Use the offscreen document's `document` interface to write a new value to the
// system clipboard.
//
// At the time this demo was created (Jan 2023) the `navigator.clipboard` API
// requires that the window is focused, but offscreen documents cannot be
// focused. As such, we have to fall back to `document.execCommand()`.
async function handleClipboardWrite(data) {
try {
// Error if we received the wrong kind of data.
if (typeof data !== 'string') {
throw new TypeError(
`Value provided must be a 'string', got '${typeof data}'.`
);
}
// `document.execCommand('copy')` works against the user's selection in a web
// page. As such, we must insert the string we want to copy to the web page
// and to select that content in the page before calling `execCommand()`.
textEl.value = data;
textEl.select();
document.execCommand('copy');
} finally {
// Job's done! Close the offscreen document.
window.close();
}
}

View File

@@ -0,0 +1,156 @@
import RecorderControl from '../classes/RecorderControl.js';
let recordingControl = new RecorderControl();
let autoFillAddressSetting;
let autoFillCreditCardSetting;
let passwordSavingSetting;
let hasPlatformAuth = false;
function addWindowInformation(events, sender, isIframe) {
for(let i=0;i<events.length;i++) {
events[i].frameId = sender.frameId;
if(sender.tab.id) {
events[i].tabId = sender.tab.id;
}
events[i].windowId = sender.tab.windowId;
if(typeof events[i].frameId === 'undefined') {
events[i].frameId = sender.tab.frameId;
}
if(typeof events[i].url !== 'string' || !/^https?:/.test(events[i].url)) {
events[i].url = sender.url;
}
events[i].isIframe = isIframe ? true : false
}
return events;
}
chrome.runtime.onInstalled.addListener(function() {
recordingControl.installed();
});
chrome.privacy.services.autofillAddressEnabled.get({}, function(details) {
autoFillAddressSetting = details.value;
});
chrome.privacy.services.autofillCreditCardEnabled.get({}, function(details) {
autoFillCreditCardSetting = details.value;
});
chrome.privacy.services.passwordSavingEnabled.get({}, function(details) {
passwordSavingSetting = details.value;
});
chrome.webNavigation.onBeforeNavigate.addListener(function(e){
if(e.frameId === 0) {
recordingControl.pageUnload();
}
});
chrome.storage.onChanged.addListener(function(changes, namespace) {
if(!changes.recording) {
return;
}
let recording = changes.recording.newValue;
if(recording) {
recordingControl.setRecordingState(recording);
chrome.privacy.services.autofillAddressEnabled.set({ value: false }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to disable auto fill address", chrome.runtime.lastError);
}
});
chrome.privacy.services.autofillCreditCardEnabled.set({ value: false }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to disable auto credit card", chrome.runtime.lastError);
}
});
chrome.privacy.services.passwordSavingEnabled.set({ value: false }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to disable password saving", chrome.runtime.lastError);
}
});
chrome.storage.sync.get('recordingDelay', function(data) {
recordingControl.start(+data.recordingDelay);
});
} else {
hasPlatformAuth = false;
chrome.privacy.services.autofillAddressEnabled.set({ value: autoFillAddressSetting }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to set auto fill address setting", chrome.runtime.lastError);
}
});
chrome.privacy.services.autofillCreditCardEnabled.set({ value: autoFillCreditCardSetting }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to set auto credit card setting", chrome.runtime.lastError);
}
});
chrome.privacy.services.passwordSavingEnabled.set({ value: passwordSavingSetting }, function() {
if (chrome.runtime.lastError) {
console.log("Failed to set password saving", chrome.runtime.lastError);
}
});
recordingControl.finish();
}
});
chrome.webRequest.onAuthRequired.addListener(
function(details){
if(details.frameId === 0 && !details.isProxy) {
hasPlatformAuth = true;
}
},
{
urls: [
'https://*/*',
'http://*/*'
],
types: ["main_frame"]
}
);
chrome.webRequest.onCompleted.addListener(function(){
if(hasPlatformAuth && recordingControl.isRecording()) {
setTimeout(function(){
recordingControl.sendWarningMessage();
}, 500);
}
},{
urls: [
'https://*/*',
'http://*/*'
],
types: ["main_frame"]
});
chrome.runtime.onMessage.addListener(function(data, sender, sendResponse){
if(data.messageType === 'collectEvents') {
recordingControl.storeData(addWindowInformation(data.events, sender, data.isIframe));
} else if(data.messageType === 'complete') {
recordingControl.complete();
} else if(data.messageType === 'getLastRecording') {
sendResponse({type:"lastRecording",lastRecording:recordingControl.getLastRecording()});
} else if(data.messageType === 'collectIframeInfo') {
recordingControl.collectIframeInfo(data.frameId, sender.tab.windowId, data.iframeInfo);
}
});
chrome.runtime.onConnect.addListener((port) => {
if (port.name === "recorder-keep-alive") {
console.log("Connected to recorder-keep-alive port");
const pingInterval = setInterval(() => {
try {
port.postMessage({
status: "ping",
});
} catch(e){
console.error("Error sending ping", e);
}
}, 10000);
port.onMessage.addListener((msg, sender, sendResponse) => {
//console.log("Message from content script:", msg);
});
port.onDisconnect.addListener(() => {
if (chrome.runtime.lastError) {
console.log("Recorder port disconnected", chrome.runtime.lastError);
}
console.log("Recorder port disconnected, clearing ping interval");
clearInterval(pingInterval);
});
}
});

View File

@@ -0,0 +1,462 @@
export default class RecorderControl {
incognito = false;
recordingDelay = 0;
recordingWindowId = -1;
recordingTabId = -1;
tabIdLookup = Object.create(null);
#sequence = [];
#recording = false;
#lastRecording = "";
#imagePath = chrome.runtime.getManifest().name === 'Burp Suite Navigation Recorder' ? '../images/' : '../navigation-recorder/images/';
#windowIds = [];
start(delay) {
this.recordingDelay = delay;
console.log('Recording...');
let that = this;
this.#sequence = [];
this.#lastRecording = '';
chrome.extension.isAllowedIncognitoAccess(function (isAllowed){
if(isAllowed) {
that.launchRecordingWindow(true);
} else {
chrome.storage.sync.get('bypassIncognito', function(data) {
if(data.bypassIncognito) {
that.launchRecordingWindow(false);
}
});
}
});
}
launchRecordingWindow(incognito) {
let that = this;
this.incognito = incognito;
chrome.windows.create({incognito, focused: true, state: 'maximized'}, function(win){
that.recordingWindowId = win.id;
that.recordingTabId = win.tabs[0].id;
chrome.tabs.onCreated.addListener(that.tabCreatedListener.bind(that));
chrome.windows.onCreated.addListener(that.windowCreatedListener.bind(that));
chrome.windows.onRemoved.addListener(that.windowRemovedListener.bind(that));
chrome.webNavigation.onDOMContentLoaded.addListener(that.tabListener.bind(that));
chrome.action.setIcon({path: that.#imagePath + 'WebApp-Recorder-recording-icon.png'});
that.manualUrlListener = that.manualUrlListener.bind(that);
chrome.webNavigation.onBeforeNavigate.addListener(that.manualUrlListener);
chrome.webNavigation.onCommitted.addListener(that.manualUrlListener);
that.tabIdLookup[that.recordingTabId] = win.id;
that.saveEvent({
name:"Burp Suite Navigation Recorder", version: chrome.runtime.getManifest().version, recordingDelay: that.recordingDelay, eventType: "start", platform: navigator.platform, iframes: [], windows: [{windowId: win.id}], tabs: [{tabId: that.recordingTabId, windowId: win.id}]
});
});
}
isRecording() {
return this.#recording;
}
getLastRecording() {
return this.#lastRecording;
}
setRecordingState(state) {
this.#recording = state;
}
async copyToClipboard(text) {
await addToClipboard(text);
}
triggersNavigation(lastEvent) {
return !!(lastEvent && lastEvent.eventType !== "start" && lastEvent.eventType !== "goto");
}
tabIdToWindowId(tabId) {
return this.tabIdLookup[tabId];
}
manualUrlListener(details) {
if(/^https?:/.test(details.url)) {
let date = new Date();
let gotoEvent = {
date: date,
timestamp: +date,
eventType: 'goto',
url: details.url,
triggersNavigation: true,
frameId: details.frameId,
tabId: details.tabId,
windowId: this.tabIdToWindowId(details.tabId)
};
let lastEvent = this.lastEvent();
if(details.transitionQualifiers && details.transitionQualifiers.includes('client_redirect')) {
let clientSideRedirect = {
date: date,
timestamp: +date,
url: details.url,
eventType: 'clientSideRedirect',
frameId: details.frameId,
tabId: details.tabId,
windowId: this.tabIdToWindowId(details.tabId)
};
if(this.triggersNavigation(lastEvent)) {
lastEvent.triggersNavigation = true;
}
this.saveEvent(clientSideRedirect);
} else if(this.triggersNavigation(lastEvent)) {
lastEvent.triggersNavigation = true;
let navigateEvent = {
date: date,
timestamp: +date,
eventType: 'userNavigate',
url: details.url,
frameId: details.frameId,
tabId: details.tabId,
windowId: this.tabIdToWindowId(details.tabId)
};
this.addStartUrl(details.url, details.tabId);
this.saveEvent(navigateEvent);
}
if(details.transitionType === 'auto_bookmark') {
gotoEvent.fromAddressBar = true;
gotoEvent.url = lastEvent.url;
this.addStartUrl(details.url, details.tabId);
} else if(details.transitionQualifiers && details.transitionQualifiers.includes('from_address_bar') && lastEvent && lastEvent.eventType === 'goto') {
gotoEvent.fromAddressBar = true;
gotoEvent.url = lastEvent.url;
this.addStartUrl(details.url, details.tabId);
} else {
gotoEvent.fromAddressBar = false;
}
if(!lastEvent) {
this.saveEvent(gotoEvent);
} else {
if(!(lastEvent.fromAddressBar && gotoEvent.fromAddressBar && gotoEvent.url === lastEvent.url)) {
this.saveEvent(gotoEvent);
}
}
}
}
addStartUrl(url, tabId) {
let element = this.#sequence[0]['tabs'].find(element => element.tabId === tabId);
if(element && !element.attributes) {
element.attributes = {firstUrl: url};
}
}
addInfoToFirstEvent(property, data, lookupPropertyName, lookupPropertyValue) {
if(!this.#sequence[0][property].find(element => element[lookupPropertyName] === lookupPropertyValue)) {
this.#sequence[0][property].push(data);
}
}
tabCreatedListener(tab) {
let that = this;
if(!this.isRecording()) {
return;
}
if(tab.windowId < that.recordingWindowId || (that.incognito && !tab.incognito)) {
return;
}
chrome.tabs.query({windowId: tab.windowId}, function(tabs){
if(!tabs.length) {
return;
}
that.addInfoToFirstEvent('tabs', {tabId: tab.id, windowId: tab.windowId, openerTabId: tab.openerTabId}, 'tabId', tab.id);
that.tabIdLookup[tab.id] = tab.windowId;
chrome.scripting.executeScript(
{
target: {tabId: tabs[0].id},
func: function(windowId, tabId){
recorder.collectEvents({createdTab:true, triggersNavigation:true, opensNewContext:true, windowId: windowId,tabId: tabId});
},
args: [tab.windowId, tabs[0].id],
injectImmediately: true
}
).catch(e => {
console.log("Failed to collect events. error:",e);
});
});
}
collectIframeInfo(frameId, windowId, iframeInfo) {
this.iframeCreated(windowId, frameId, iframeInfo);
}
iframeCreated(windowId, frameId, iframeInfo) {
let firstEvent = this.#sequence[0];
for(let i=0;i<firstEvent.iframes.length;i++) {
if(firstEvent.iframes[i].frameId === frameId) {
return;
}
}
firstEvent.iframes.push({frameId: frameId, createdByWindowId: windowId, ...iframeInfo});
}
windowCreatedListener(win) {
if(!this.isRecording()) {
return;
}
if(win.id < this.recordingWindowId || (this.incognito && !win.incognito)) {
return;
}
if(win.id !== this.recordingWindowId) {
this.#windowIds.push(win.id);
let tabId = -1;
chrome.tabs.query({windowId: win.id}, function(tabs){
that.tabIdLookup[tabs[0].id] = win.id;
tabId = tabs[0].id;
});
let that = this;
chrome.tabs.query({windowId: this.recordingWindowId}, function(tabs){
if(!tabs.length) {
return;
}
that.addInfoToFirstEvent('windows', {windowId: win.id, state: win.state}, 'windowId', win.id);
chrome.scripting.executeScript(
{
target: {tabId: tabs[0].id},
func: function(windowId, tabId){
recorder.collectEvents({createdWindow:true, triggersNavigation:true, opensNewContext:true, windowId: windowId, tabId: tabId});
},
args: [win.id, tabs[0].id],
injectImmediately: true
}
).catch(e => {
console.log("Failed to collect events. error:",e);
});
});
}
}
findLastEvent(excludeTypeRegex, callback){
let len = this.#sequence.length;
if(len) {
for(let i=len-1;i>0;i--) {
if(this.#sequence[i] && !excludeTypeRegex.test(this.#sequence[i].eventType)) {
callback(i, this.#sequence);
return;
}
}
}
}
windowRemovedListener(winId) {
if(this.isRecording() && winId !== this.recordingWindowId) {
if(this.#sequence.length) {
this.findLastEvent(/^(?:goto|userNavigate)$/, function(pos, obj){
obj[pos].closesContext = true;
});
}
}
if(this.isRecording() && winId === this.recordingWindowId) {
chrome.storage.sync.set({recording: false, complete:true});
this.complete();
}
}
tabListener(details) {
let delay = this.recordingDelay;
let that = this;
if(this.isRecording()) {
chrome.tabs.get(details.tabId, function(tab) {
if(tab.windowId < that.recordingWindowId || (that.incognito && !tab.incognito)) {
return;
}
chrome.scripting.executeScript(
{
target: {tabId: details.tabId, frameIds:[details.frameId]},
func: function(frameId, tabId, delay){
window.recorder.start(frameId,tabId,delay);
},
args: [details.frameId, details.tabId, delay],
injectImmediately: true
}
).catch(e => {
console.log("Failed to execute JavaScript:",e);
});
});
}
}
lastEvent() {
return this.#sequence[this.#sequence.length-1];
}
saveEvent(event) {
this.#sequence.push(event);
}
addWindowTabInformation(events) {
let firstEvent = this.#sequence[0];
for(let i = 0; i < events.length;i++) {
let event = events[i];
if(event.createdTabId) {
for(let j=0;j<firstEvent.tabs.length;j++) {
if(firstEvent.tabs[j].tabId === event.createdTabId && firstEvent.tabs[j].windowId === event.windowId && event.opensNewContext) {
firstEvent.tabs[j].createdByEvent = i;
break;
}
}
} else if(event.createdWindowId) {
for(let j=0;j<firstEvent.windows.length;j++) {
if(firstEvent.windows[j].windowId === event.createdWindowId && firstEvent.windows[j].tabId === event.tabId && event.opensNewContext) {
firstEvent.windows[j].createdByEvent = i;
break;
}
}
}
}
}
storeData(data) {
this.#sequence = this.#sequence.concat(data);
}
installed() {
console.log("Extension installed");
}
pageUnload() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
if(!tabs.length) {
return;
}
if(tabs[0]) {
chrome.scripting.executeScript(
{
target: {tabId: tabs[0].id},
func: function(){
recorder.collectEvents();
},
injectImmediately: true
}
).catch(e => {
console.log("Failed to collect events. error:",e);
});
} else {
console.log("Unable to collect events tab doesn't exist");
}
});
}
finish() {
console.log('Stopped recording.');
chrome.tabs.query({windowId: this.recordingWindowId}, function(tabs) {
if(!tabs.length) {
return;
}
chrome.scripting.executeScript(
{
target: {tabId: tabs[0].id},
func: function(){
recorder.finish();
},
injectImmediately: true
}
).catch(e => {
console.log("Error closing window",e);
});
});
chrome.action.setIcon({path: this.#imagePath + 'WebApp-Recorder-stop-icon.png'});
}
sendWarningMessage() {
if(!this.isRecording()) {
return;
}
chrome.tabs.query({windowId: this.recordingWindowId}, function(tabs) {
chrome.scripting.executeScript(
{
target: {tabId: tabs[0].id},
func: function(){
recorder.showWarningMessage();
},
injectImmediately: true
}
).catch(e => {
console.log("Failed to send warning message",e);
});
});
}
filterEvents(events) {
let filtered = [];
for(let i=0;i<events.length;i++) {
let event = events[i];
let lastEvent = events[i-1];
if(event.eventType === 'goto') {
if(!event.fromAddressBar) {
continue;
} else {
if(lastEvent && lastEvent.eventType === 'goto') {
let lastFiltered = filtered[filtered.length-1];
if(lastFiltered && lastFiltered.eventType !== 'start') {
filtered[filtered.length-1] = event;
} else {
filtered.push(event);
}
} else {
filtered.push(event);
}
}
} else {
filtered.push(event);
}
}
return filtered;
}
async complete() {
let filteredEvents = this.filterEvents(this.#sequence);
this.addWindowTabInformation(filteredEvents);
if(this.#sequence.length) {
try {
this.#lastRecording = JSON.stringify(filteredEvents, undefined, 4);
} catch(e) {
console.log("Failed to save JSON"+e);
}
}
chrome.notifications.create("notify", {iconUrl: this.#imagePath + "WebApp-Recorder-128.png", title:"Burp Suite navigation recorder", type:"basic", message:"Your recording has finished and has been copied to your clipboard."});
for(const winId of this.#windowIds) {
try {
await chrome.windows.remove(winId);
} catch(e) {
console.log("Could not close window. Already closed.");
}
}
if(this.recordingWindowId > 0) {
try {
await chrome.windows.remove(this.recordingWindowId);
} catch(e){
console.log("Could not close original recording window. User already closed it.");
} finally {
await this.copyToClipboard(this.getLastRecording());
this.#sequence = [];
this.removeListeners();
this.setRecordingState(false);
}
}
}
removeListeners() {
chrome.tabs.onCreated.removeListener(this.tabCreatedListener);
chrome.windows.onCreated.removeListener(this.windowCreatedListener);
chrome.windows.onRemoved.removeListener(this.windowRemovedListener);
chrome.webNavigation.onDOMContentLoaded.removeListener(this.tabListener);
chrome.webNavigation.onBeforeNavigate.removeListener(this.manualUrlListener);
chrome.webNavigation.onCommitted.removeListener(this.manualUrlListener);
}
}
let creating; // A global promise to avoid concurrency issues
async function setupOffscreenDocument(path) {
// Check all windows controlled by the service worker to see if one
// of them is the offscreen document with the given path
const offscreenUrl = chrome.runtime.getURL(path);
const existingContexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl]
});
if (existingContexts.length > 0) {
return;
}
// create offscreen document
if (creating) {
await creating;
} else {
creating = chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Write text to the clipboard.',
});
await creating;
creating = null;
}
}
async function addToClipboard(value) {
await setupOffscreenDocument(chrome.runtime.getManifest().name === 'Burp Suite Navigation Recorder' ? 'background/offscreen.html' : "navigation-recorder/background/offscreen.html");
chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: value
});
}

View File

@@ -0,0 +1,630 @@
class Recorder {
recordingDelay = 0;
maxDelay = 10000;
tabId = -1;
frameId = -1;
lastElement = null;
lastEvent = null;
lastEventTime = null;
#events = [];
#started = false;
start(frameId, tabId, delay) {
console.log("Recorder has started");
this.#started = true;
this.recordingDelay = delay;
document.body.style.border='8px solid red';
this.addListeners();
this.frameId = frameId;
this.tabId = tabId;
if(top !== self) {
this.sendFrameId();
} else {
this.addMessageListener();
const port = chrome.runtime.connect({ name: "recorder-keep-alive" });
port.onMessage.addListener((msg) => {
//console.log("Message from recorder service worker:", msg);
port.postMessage({status: "pong"});
});
}
}
isStarted() {
return this.#started;
}
useDefaultStyles(element) {
element.style.all = 'unset';
if(element instanceof HTMLHeadingElement && element.tagName === 'H1') {
element.style.display = 'block';
element.style.fontSize = "2em";
element.style.marginTop = "0.67em";
element.style.marginBottom = "0.67em";
element.style.marginLeft = "0";
element.style.marginRight = "0";
element.fontWeight = "bold";
} else if(element instanceof HTMLParagraphElement) {
element.style.display = 'block';
element.style.marginTop = "1em";
element.style.marginBottom = "1em";
element.style.marginLeft = "0";
element.style.marginRight = "0";
} else if(element instanceof HTMLUListElement) {
element.style.display = "block";
element.style.listStyleType = "disc";
element.style.marginTop = "1em";
element.style.marginBottom = "1em";
element.style.marginLeft = "0";
element.style.marginRight = "0";
element.style.paddingLeft = "40px";
} else if(element instanceof HTMLLIElement) {
element.style.display = "list-item";
}
return element;
}
showWarningMessage() {
let container = document.createElement('div');
container.style.all = 'unset';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'center';
container.style.zIndex = "9999999";
container.style.position = 'fixed';
container.style.width = '100%';
container.style.height = '100%';
container.style.left = "0";
container.style.top = "0";
let div = document.createElement('div');
div.style.all = 'unset';
div.style.backgroundColor = '#f8d7da';
div.style.color = '#721c24';
div.style.border = '1px solid #f5c6cb';
div.style.width = '500px';
div.style.height = '350px';
div.style.borderRadius = '12px';
div.style.padding = '10px';
div.style.fontFamily = 'Arial';
let heading = document.createElement('h1');
heading = this.useDefaultStyles(heading);
heading.textContent = 'Warning';
div.append(heading);
let p = document.createElement('p');
p = this.useDefaultStyles(p);
p.textContent = "This recording will not work in Burp, as the application uses platform authentication.";
div.append(p);
let p2 = document.createElement('p');
p2 = this.useDefaultStyles(p2);
p2.textContent = "To configure platform authentication credentials, you need to manually add the username and password in Burp:";
div.append(p2);
let ul = this.useDefaultStyles(document.createElement("ul"));
let li = this.useDefaultStyles(document.createElement("li"));
li.innerHTML = 'In Burp Suite Professional, do this in the <b>Settings</b> dialog, under <b>Connections &gt; Platform authentication</b>. For more information, see <a href="https://portswigger.net/burp/documentation/desktop/settings/network/connections#platform-authentication" style="all:revert;color:black">Connections - Platform authentication</a>.';
let li2 = this.useDefaultStyles(document.createElement("li"));
li2.innerHTML = 'In Burp Suite Enterprise Edition, do this in the <b>Scan settings</b> for your site or folder, under <b>Authentication &gt; Platform authentication</b>. For more information, see <a href="https://portswigger.net/burp/documentation/enterprise/working-with-sites/site-settings/configure-authentication/platform-authentication.html" style="all:revert;color:black">Configuring platform authentication</a>.';
ul.append(li);
ul.append(li2);
div.append(ul);
container.append(div);
document.body.append(container);
}
sendFrameId() {
top.postMessage({frameId: this.frameId, messageType: "Burp Suite Navigation Recorder frameId"}, '*');
}
getAllAttributesForNode(node) {
let attributes = {};
for(let i=0;i<node.attributes.length;i++){
attributes[node.attributes[i].nodeName] = node.attributes[i].nodeValue;
}
return attributes;
}
getAllDataAttributesForNode(node) {
return JSON.stringify(node.dataset);
}
addMessageListener() {
let that = this;
window.addEventListener('message', function(e){
let data = e.data;
if(typeof data === 'object' && e.data.messageType === 'Burp Suite Navigation Recorder frameId') {
let iframes = document.querySelectorAll('iframe');
for(let iframe of iframes) {
if(iframe.contentWindow === e.source) {
chrome.runtime.sendMessage({messageType:'collectIframeInfo', frameId: e.data.frameId, iframeInfo: {
xPath: that.getXPath(iframe),
tagNodeIndex: that.getTagIndex(iframe),
attributes: that.getAllAttributesForNode(iframe),
dataAttributes: that.getAllDataAttributesForNode(iframe),
iframeIndex: that.getIframeIndex(iframe)
}
});
break;
}
}
} else if(typeof data === 'object' && e.data.messageType === 'Burp Suite Navigation Recorder collect') {
that.collectEvents();
}
})
}
isPasteEvent(e) {
return e.key === 'v' && (e.ctrlKey || e.metaKey);
}
collectEvents(config) {
if(this.#events.length) {
if(config && config.triggersNavigation) {
this.#events[this.#events.length-1].triggersNavigation = true;
}
if(config && config.opensNewContext) {
this.#events[this.#events.length-1].opensNewContext = true;
}
if(config && config.createdTab) {
this.#events[this.#events.length-1].createdTabId = config.tabId;
}
if(config && config.createdWindow) {
this.#events[this.#events.length-1].createdWindowId = config.windowId;
this.#events[this.#events.length-1].createdTabId = config.tabId;
}
if(config && config.windowId) {
this.#events[this.#events.length - 1].windowId = config.windowId;
}
if(config && config.tabId) {
this.#events[this.#events.length - 1].tabId = config.tabId;
}
chrome.runtime.sendMessage({messageType:'collectEvents', isIframe: top!==self, events:this.filterEvents(this.#events)});
this.#events = [];
}
}
executeXPath(expression) {
try {
let it = document.evaluate(expression, document, function (prefix) {
if (prefix === 'svg') {
return 'http://www.w3.org/2000/svg';
} else {
return null;
}
}, XPathResult.ANY_TYPE, null);
return it.iterateNext();
} catch(e){console.error("Navigation Recorder error invalid xpath:" + e);}
return;
}
filterEvents(events) {
let filtered = [];
let chunk = [];
for(let i=0;i<events.length;i++) {
let event = events[i];
delete events[i].element;
if(event.eventType === 'click' || event.eventType === 'typing' || event.eventType === 'scroll' || event.eventType === 'keyboard') {
filtered = filtered.concat(chunk);
filtered.push(event);
chunk = [];
} else {
chunk.push(event);
}
}
return filtered;
}
getNamespacePrefix(element) {
if(element instanceof SVGElement) {
return "svg:";
}
return "";
}
getTextFromAccessibleAttributes(element){
let text = [];
if(/^input$/i.test(element.tagName) && /^button|submit$/i.test(element.type)) {
text.push(element.value);
}
let ariaLabel = element.getAttribute('aria-label');
if(ariaLabel !== null) {
text.push(ariaLabel);
}
let alt = element.getAttribute("alt");
if(alt !== null) {
text.push(alt);
}
let title = element.getAttribute("title");
if(title !== null) {
text.push(title);
}
return text;
}
isCustomElement(element) {
return element.tagName.includes("-");
}
getXPath(element){
let ele = element;
let path = [];
let result;
while(ele) {
let pos = 0;
let sibling = ele.previousElementSibling;
while(sibling) {
if(sibling.nodeName === ele.nodeName) {
pos++;
}
sibling = sibling.previousElementSibling;
}
let exp = this.getNamespacePrefix(ele) + ele.nodeName.toLowerCase();
if(pos > 0) {
exp = exp + '['+(pos+1)+']';
} else if(pos === 0 && ele.nextElementSibling && ele.nextElementSibling.nodeName === ele.nodeName) {
exp = exp + '[1]';
}
if(ele !== document) {
path.unshift(exp);
}
ele = ele.parentNode;
}
result = '/' + path.join('/');
if(this.executeXPath(result) === element) {
return result;
}
}
complete() {
chrome.runtime.sendMessage({messageType:'complete'});
}
saveEvent(event) {
if(this.recordingDelay) {
this.addDelay(this.recordingDelay)
}
event.url = location+'';
this.#events.push(event);
}
addDelay(delay) {
if(this.lastEventTime === null) {
this.lastEventTime = Date.now();
} else {
let lastEventTime= this.lastEventTime
let now = Date.now();
let durationInMillis = Math.floor(now - lastEventTime);
if(delay > 1) {
durationInMillis = this.clamp(Math.floor(durationInMillis * delay), this.maxDelay);
}
let delayEvent = {eventType: "wait", durationInMillis};
this.lastEventTime = now;
this.#events.push(delayEvent);
}
}
clamp(value, max) {
return Math.min(value, max);
}
addListeners() {
let that = this;
document.addEventListener('close', this.dialogClose, true);
document.addEventListener('contextmenu', this.intercept, true);
document.addEventListener('paste', this.intercept, true);
document.addEventListener('keydown', this.intercept, true);
document.addEventListener('click', this.intercept, true);
document.addEventListener('scroll', this.intercept, true);
window.addEventListener('hashchange', this.intercept, true);
if(top !== self) {
window.addEventListener('focus', function(){
top.postMessage({messageType: 'Burp Suite Navigation Recorder collect'}, "*");
}, true)
window.addEventListener('blur', function(){
that.collectEvents();
}, true);
}
window.addEventListener('beforeunload', function(){
that.collectEvents();
}, true);
}
removeListeners() {
document.removeEventListener('close', this.dialogClose, true);
document.removeEventListener('contextmenu', this.intercept, true);
document.removeEventListener('paste', this.intercept, true);
document.removeEventListener('keydown', this.intercept, true);
document.removeEventListener('click', this.intercept, true);
document.removeEventListener('scroll', this.intercept, true);
window.removeEventListener('hashchange', this.intercept, true);
}
finish() {
this.collectEvents();
this.complete();
this.stop();
}
stop() {
if(this.#started) {
this.#started = false;
document.body.style.border="none";
this.removeListeners();
}
}
captureChange(element) {
element.addEventListener('change', function f(e){
recorder.createClickEvent(e);
element.removeEventListener('change', f, true);
}, true);
}
dialogClose(e) {
let recorder = window.recorder;
recorder.collectEvents();
}
intercept(e) {
let recorder = window.recorder;
if(e.type === 'click') {
let closestAnchor = e.target.closest("a,button");
if(closestAnchor) {
recorder.createClickEvent(e, closestAnchor);
return;
}
let lastEvent = recorder.getLastEvent();
if(lastEvent && lastEvent.eventType === 'keyboard' && lastEvent.key === 'Enter' && e.target && e.target.form && !lastEvent.causesFormSubmission) {
lastEvent.causesFormSubmission = true;
return;//don't create click events when pressing the return key
}
recorder.createClickEvent(e);
if(e.target instanceof HTMLSelectElement) {
recorder.captureChange(e.target);
}
recorder.lastEvent = e;
} else if(e.type === 'hashchange') {
let lastEvent = recorder.getLastEvent();
if(lastEvent) {
lastEvent.triggersWithinDocumentNavigation = true;
}
} else if(e.type === 'scroll') {
recorder.createScrollEvent(e);
} else if(e.type === 'contextmenu') {
recorder.createClickEvent(e);
} else if(e.type === 'paste') {
let text = e.clipboardData.getData('text/plain');
if(typeof text === 'string' && text.length) {
recorder.createTypingEvent(e, text);
}
} else if (e.type === 'keydown') {
if(e.key.length === 1 && !recorder.isPasteEvent(e)) {
recorder.createTypingEvent(e, e.key);
} else {
//https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
switch(e.key) {
//Modifier keys
//The Alt key is intentially omitted. Since the typing event consoldates all character key presses.
//case "Alt":
case "AltGraph":
//The Capslock key is intentially omitted. Since the typing event consoldates all character key presses.
//case "CapsLock":
//We don't want to interfere with copy/paste actions
//case "Control":
//case "Meta":
case "Fn":
case "FnLock":
case "Hyper":
case "NumLock":
case "ScrollLock":
//The shift key is intentially omitted. Since the typing event consoldates all character key presses.
//case "Shift":
case "Super":
case "Symbol":
case "SymbolLock":
//whitespace keys
case "Tab":
case "Enter":
//navigation keys
case "ArrowDown":
case "ArrowLeft":
case "ArrowRight":
case "ArrowUp":
case "End":
case "Home":
case "PageDown":
case "PageUp":
//editing keys
case "Backspace":
case "Clear":
case "Copy":
case "CrSel":
case "Cut":
case "Delete":
case "EraseEof":
case "ExSel":
case "Insert":
case "Paste":
case "Redo":
case "Undo":
case "Accept":
case "Again":
case "Attn":
case "Cancel":
case "ContextMenu":
case "Escape":
case "Execute":
case "Find":
case "Finish":
case "Help":
case "Pause":
case "Play":
case "Props":
case "Select":
case "ZoomIn":
case "ZoomOut":
//device keys
case "BrightnessDown":
case "BrightnessUp":
case "Eject":
case "LogOff":
case "Power":
case "PowerOff":
case "PrintScreen":
case "Hibernate":
case "Standby":
case "WakeUp":
recorder.createKeyboardEvent(e);
break;
}
}
}
}
createScrollEvent(e) {
let lastEvent = this.getLastEvent();
let date = new Date();
if(lastEvent && lastEvent.eventType === 'scroll') {
lastEvent.scrollX = window.scrollX;
lastEvent.scrollY = window.scrollY;
} else {
let scrollEvent = {
date: date,
timestamp: +date,
eventType: 'scroll',
scrollX: window.scrollX,
scrollY: window.scrollY,
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
};
this.saveEvent(scrollEvent);
}
}
createTypingEvent(e, text) {
let lastEvent = this.getLastEvent();
if(lastEvent && lastEvent.eventType === 'typing' && e.target === this.getLastElement()) {
lastEvent.typedValue += text;
} else {
let typingEvent = this.createEvent(e.target, 'typing', e);
typingEvent.typedValue = text;
this.saveEvent(typingEvent);
}
this.lastElement = e.target;
}
createEvent(element, type, browserEvent) {
let host = null;
let path = browserEvent.composedPath();
let firstElementInPath = null;
if(path !== null) {
firstElementInPath = path.shift();
}
let isShadowElement = firstElementInPath === null || element === firstElementInPath ? false : !!element.shadowRoot;
if(isShadowElement) {
host = element;
element = firstElementInPath;
}
let date = new Date;
let event = {
date: date,
timestamp: +date,
windowInnerWidth: window.innerWidth,
windowInnerHeight: window.innerHeight,
eventType: type,
frameId: this.frameId,
tabId: this.tabId,
triggersNavigation: false,
triggersWithinDocumentNavigation: false,
isShadowElement,
...(isShadowElement && {shadowParent: this.generateElementAttributes(host, false)}),
...(this.generateElementAttributes(element, isShadowElement, isShadowElement ? host.shadowRoot : null))
};
return event;
}
generateElementAttributes(element, isShadowElement, shadowRoot) {
return {
tagName: element.tagName,
placeholder: element.placeholder,
href: this.getAttributeValue(element, 'href'),
src: this.getAttributeValue(element, 'src'),
ariaLabel: this.getAttributeValue(element, 'aria-label'),
className: element.className,
name: element.name,
id: element.id,
textContent: element?.textContent?.slice(0,100),
innerHTML: element?.innerHTML?.slice(0,100),
text: element && element.form && element.tagName === 'SELECT' ? element[element.selectedIndex].text : undefined,
selectedIndex: element.selectedIndex,
value: typeof element.value === 'undefined' ? undefined : String(element.value),
elementType: element.type,
textNodes: this.getTextNodes(element),
tagNodeIndex: this.getTagIndex(element, isShadowElement, shadowRoot),
...(!isShadowElement && {xPath: this.getXPath(element)})
};
}
getTextNodes(element) {
let textNodes = [];
function getChildTextNodes(node) {
node.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE) {
let trimmedText = child.textContent.trim();
if (trimmedText.length > 0) {
textNodes.push(trimmedText);
}
} else {
getChildTextNodes(child);
}
});
}
getChildTextNodes(element);
return textNodes;
}
createKeyboardEvent(e) {
let date = new Date();
let keyboardEvent = {
date: date,
timestamp: +date,
eventType: 'keyboard',
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey,
key: e.key,
charCode: e.charCode
};
this.saveEvent(keyboardEvent);
}
createClickEvent(e, otherTarget) {
let lastEvent = this.getLastEvent();
let clickEvent = this.createEvent(otherTarget ? otherTarget : e.target, e.type === 'contextmenu' ? 'rightClick' : 'click', e);
clickEvent.characterPos = e.target.selectionStart;
clickEvent.shiftKey = e.shiftKey;
clickEvent.ctrlKey = e.ctrlKey;
clickEvent.altKey = e.altKey;
clickEvent.metaKey = e.metaKey;
clickEvent.element = otherTarget ? otherTarget : e.target;
if(lastEvent && typeof lastEvent.tagName === 'string' && lastEvent.tagName.toLowerCase() === 'label' && lastEvent.eventType === 'click') {
let labelElement = lastEvent.element;
if(clickEvent.element && labelElement && clickEvent.element.labels && clickEvent.element.labels.length && Array.from(clickEvent.element.labels).includes(labelElement)) {
return;
}
}
this.saveEvent(clickEvent);
}
getIframeIndex(element) {
for(let i=0;i<window.length;i++) {
if(window[i] === element.contentWindow) {
return i;
}
}
return -1;
}
getAttributeValue(element, attributeName) {
if(!element.getAttribute) {
return;
}
let value = element.getAttribute(attributeName);
if(value === null) {
return;
}
// Truncate attribute values because of data: URLs such as data:image
return String(value).substring(0, 500);
}
getTagIndex(element, isShadowElement = false, shadowRoot) {
let doc = isShadowElement ? shadowRoot : document;
let tags = doc.querySelectorAll(element.tagName);
for(let i=0;i<tags.length;i++) {
if(element === tags[i]) {
return i;
}
}
return -1;
}
getLastElement() {
return this.lastElement;
}
getLastEvent() {
return this.#events[this.#events.length-1];
}
}
window.recorder = new Recorder();

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 17"><path d="M2.3.3l.1.1 6 6 6-6c.6-.6 1.5-.6 2 0 .5.5.6 1.3.1 1.9l-.1.1-6 6 6 6c.6.6.6 1.5 0 2-.5.5-1.3.6-1.9.1l-.1-.1-6-6-6 6c-.6.6-1.5.6-2 0-.5-.5-.6-1.3-.1-1.9l.1-.1 6-6-6-6C-.2 1.8-.2.9.4.4.9-.1 1.8-.1 2.3.3z" fill-rule="evenodd" clip-rule="evenodd" fill="#333332"/></svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57 66"><path d="M41.9 0H6C2.7 0 0 2.7 0 6v41.9h6V6h35.9V0z" fill="#8c8d8e"/><path d="M50.9 12H18c-3.3 0-6 2.7-6 6v41.9c0 3.3 2.7 6 6 6h33c3.3 0 6-2.7 6-6V18c-.1-3.3-2.8-6-6.1-6zm0 47.9H18V18h33v41.9h-.1z" fill="#ff6524"/></svg>

After

Width:  |  Height:  |  Size: 280 B

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 75 44" style="enable-background:new 0 0 75 44;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FF6524;}
.st1{fill:#8C8D8E;}
</style>
<desc>Created with Sketch.</desc>
<g id="Page-1">
<g id="_x32_" transform="translate(-1649.000000, -122.000000)">
<g id="video-camera" transform="translate(1649.500000, 122.000000)">
<path id="Combined-Shape" class="st0" d="M49.6,0c2.5,0,4.5,2,4.5,4.5l0,0l0,4.9L65.7,2c1.5-1,3.3-1.1,5-0.3l0.2,0.1
c1.7,0.9,2.8,2.7,2.8,4.7l0,0v31.1c0,2-1.1,3.7-2.8,4.7c-0.8,0.4-1.6,0.6-2.4,0.6c-1,0-2-0.3-2.8-0.8l0,0l-11.5-7.3l0,4.8
c0,2.4-1.9,4.4-4.3,4.5l-0.2,0H4.5C2,44,0,41.9,0,39.5l0,0V4.5C0,2.1,2,0,4.5,0l0,0H49.6z M48.7,5.4H5.4v33.1h43.3V5.4z
M68.2,6.8l-14.1,9.1v12.4l14.1,8.9V6.8z"/>
<path id="Path" class="st1" d="M31.2,11.9H13.6c-1.5,0-2.7,1.2-2.7,2.7c0,1.5,1.2,2.7,2.7,2.7h17.6c1.5,0,2.7-1.2,2.7-2.7
S32.7,11.9,31.2,11.9z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,37 @@
{
"action": {
"default_icon": {
"128": "images/WebApp-Recorder-128.png",
"16": "images/WebApp-Recorder-16.png",
"32": "images/WebApp-Recorder-32.png",
"48": "images/WebApp-Recorder-48.png"
},
"default_popup": "./popup/popup.html",
"default_title": "Burp Suite Navigation Recorder"
},
"background": {
"service_worker": "./background/recorder-service-worker.js",
"type": "module"
},
"content_scripts": [ {
"all_frames": true,
"js": [ "./content-scripts/classes/Recorder.js" ],
"match_about_blank": true,
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
} ],
"description": "Improve your Burp Suite scan coverage by manually capturing how to perform complex actions on your website.",
"host_permissions": [ "https://*/*", "http://*/*" ],
"icons": {
"128": "images/WebApp-Recorder-128.png",
"16": "images/WebApp-Recorder-16.png",
"32": "images/WebApp-Recorder-32.png",
"48": "images/WebApp-Recorder-48.png"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApE1NX2B8te2W1JPYurplK2oLP7iJskIDXYQ6lTDq4HWLQ5hKULTOb9NJxe0RNGdEIDV2DSvA42B58p0ee9SemLwVPb5Jrl5oy8xA1h7h86C0MxLLBuwtOyj+ZodJvY0sW4mWwo+aOe1zsThJRjOKgo2nY3xBqvNBQy1lk8WvjVRnhNcNFDqJhEZORYor+CR0jYELjmkdntfnevVC30PvW4KWysHlFkvRXg/oioPqTLoVtMSL30I0UclLINOIWrH4gZ2oq9a3C/grsgsriKnG5/vXCY7MLONrxvUeD+DELUyoLLvzVNHvoUGkC840sJIcxziB7PEEnLOjtay2uu7ZCQIDAQAB",
"manifest_version": 3,
"name": "Burp Suite Navigation Recorder",
"permissions": [ "activeTab", "storage", "tabs", "clipboardWrite", "webNavigation", "privacy", "notifications", "webRequest", "scripting", "offscreen" ],
"update_url": "https://clients2.google.com/service/update2/crx",
"version": "2.0.13"
}

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="styles/styles.css" />
<title>Burp Suite Navigation Recorder</title>
</head>
<body class="completePopup">
<div class="logo"><img src="../images/WebApp-Recorder-32.png" alt="Burp Suite Navigation Recorder"/></div>
<div class="Aligner">
<div class="Aligner-item">
<img src="../images/copy-to-clipboard.svg" alt="Copy to Clipboard" width="58" height="67" />
<h1 id="title">Recording complete</h1>
<p id="message">Click the button below to copy your recording to the clipboard.</p>
<ul>
<li><button id="copyClipboardBtn">Copy to clipboard</button></li>
<li><button id="clearClipboardBtn">Clear clipboard</button></li>
<li><button id="newRecordingBtn">New recording</button></li>
</ul>
<p class="hide recordingDelayContainer">
<label for="recordingDelay" class="form-label" id="delayText">Recording delay (none)</label><br>
<input type="range" class="form-range" id="recordingDelay" value="0" min="0" max="2" step="1">
</p>
</div>
</div>
<div id="version">Version: {version}</div>
<script src="js/complete.js"></script>
<script src="js/shared.js"></script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles/styles.css" />
<title>Burp Suite Navigation Recorder Help</title>
</head>
<body class="helpPopup">
<div><a href="#" class="close"><img src="../images/close.svg" width="15" height="15" /></a></div>
<h1>Burp Suite Navigation Recorder</h1>
<p>Improve your Burp Suite scan coverage by manually capturing how to perform complex actions on your website.</p>
<p>Burp Suite Navigation Recorder is a Chrome extension that enables you to record complex navigation sequences, such as SSO logins, using your browser. You can then import the recording into Burp Suite Professional and Burp Suite Enterprise so that any future scans of the website can replicate your recorded actions. This can improve your Burp Suite scan coverage by increasing the attack surface that the Scanner is able to audit effectively.</p>
<p>To record an action sequence:</p>
<ol>
<li>Click the Burp Suite Navigation Recorder extension icon at the top right.</li>
<li>Click start recording.</li>
<li>Load the web page where you want to begin capturing and carry out the action sequence.</li>
<li>Click the extension icon to stop recording and click copy to clipboard to save the data from the recording to your clipboard in JSON format.</li>
<li>Paste the JSON from your clipboard into Burp Suite.</li>
</ol>
<p>Note that this extension works by recording clicks, pasted data, and keystrokes. To ensure that your action sequence is recorded properly, please avoid using any autocomplete functionality.</p>
<p>The recorded data on your clipboard will be automatically cleared when you paste it into Burp Suite.</p>
<script src="js/help.js"></script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
let lastRecording = '';
let copyClipboardBtn = document.getElementById('copyClipboardBtn');
let newRecordingBtn = document.getElementById('newRecordingBtn');
let clearClipboardBtn = document.getElementById('clearClipboardBtn');
clearClipboardBtn.addEventListener('click', async function(){
await addToClipboard(' ');
});
newRecordingBtn.addEventListener('click', function(){
chrome.storage.sync.set({recordingDelay: +document.querySelector('#recordingDelay').value,recording: true, complete:false}, function() {
console.log("Started recording");
top.close();
});
});
window.addEventListener('load', function(){
chrome.runtime.sendMessage({messageType:'getLastRecording'}, function(response){
if(response && response.type === 'lastRecording') {
lastRecording = response.lastRecording;
if(!lastRecording.length) {
chrome.storage.sync.set({recording: false, complete:false}, function() {
location = 'popup.html';
});
}
}
});
});
copyClipboardBtn.addEventListener('click', async function(){
let title = document.getElementById('title');
let message = document.getElementById('message');
title.innerHTML = 'Data copied to clipboard';
message.textContent = 'Your data has been saved to your clipboard. Please paste this into Burp Suite.';
await addToClipboard(lastRecording);
});
let creating; // A global promise to avoid concurrency issues
async function setupOffscreenDocument(path) {
// Check all windows controlled by the service worker to see if one
// of them is the offscreen document with the given path
const offscreenUrl = chrome.runtime.getURL(path);
const existingContexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl]
});
if (existingContexts.length > 0) {
return;
}
// create offscreen document
if (creating) {
await creating;
} else {
creating = chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Write text to the clipboard.',
});
await creating;
creating = null;
}
}
async function addToClipboard(value) {
await setupOffscreenDocument(chrome.runtime.getManifest().name === 'Burp Suite Navigation Recorder' ? 'background/offscreen.html' : "navigation-recorder/background/offscreen.html");
chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: value
});
}

Some files were not shown because too many files have changed in this diff Show More