├── LICENSE ├── Makefile ├── README.md ├── background.js ├── icons ├── cloudhole-32.png ├── cloudhole-48.png └── cloudhole.png ├── manifest.json └── popup ├── settings.css ├── settings.html └── settings.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 scakemyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: clean 2 | mkdir -p build 3 | git archive master | tar -x -C build 4 | $(MAKE) -C build build 5 | 6 | clean: 7 | rm -rf build 8 | 9 | build: 10 | web-ext build 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudHole 2 | 3 | Firefox web extension for cross-website CloudFlare cookies. 4 | 5 | ### Installation 6 | Install like any other Firefox add-on from the [CloudHole add-on page](https://addons.mozilla.org/addon/cloudhole/) on addons.mozilla.org or from [about:addons](about:addons) 7 | 8 | #### Description 9 | Tired of solving CloudFlare captchas for half the websites you visit because you're using a VPN or TOR? This add-on is for you. 10 | 11 | CloudFlare is a great service, and there's nothing wrong with having to solve captchas to prove you're not a robot, but it can get very tedious since their clearance cookie is not applied to other websites, prompting you to solve a captcha for each and every website you visit that is using their service. 12 | 13 | This add-on stores the user agent and clearance cookie when you solve a captcha, and re-uses it on other websites as long as it's still valid, easing the pain during your browsing session. 14 | 15 | It can also fetch and share clearance cookies with the CloudHole API, allowing you and other users to crowdsource valid cookies, either for use on other devices, embedded systems or API calls which can't use a full browser to solve those captchas. 16 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var apiKey = 'xxxxxxx'.replace(/[xy]/g, function(c) { 4 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 5 | return v.toString(16).toUpperCase(); 6 | }); 7 | var clearancesApi = "https://cloudhole.herokuapp.com"; 8 | var surgeClearances = "https://cloudhole.surge.sh/cloudhole.json"; 9 | var userAgent = ""; 10 | var clearance = ""; 11 | var _id = ""; 12 | var failing = {}; 13 | var reloaded = {}; 14 | var sendClearance = {}; 15 | var useBrowserAgent = {}; 16 | var useCloudHoleAPI = true; 17 | 18 | var setUserAgent = function(newUserAgent) { 19 | _id = ""; 20 | userAgent = newUserAgent; 21 | chrome.storage.local.set({"userAgent": newUserAgent}); 22 | } 23 | var setClearance = function(newClearance) { 24 | _id = ""; 25 | clearance = newClearance; 26 | chrome.storage.local.set({"clearance": newClearance}); 27 | } 28 | var setApiKey = function(newApiKey) { 29 | _id = ""; 30 | apiKey = newApiKey; 31 | chrome.storage.local.set({"apiKey": newApiKey}); 32 | } 33 | var setUseCloudHoleAPI = function(value) { 34 | useCloudHoleAPI = value; 35 | chrome.storage.local.set({"useCloudHoleAPI": value}); 36 | if (value == false) { 37 | _id = ""; 38 | userAgent = ""; 39 | clearance = ""; 40 | } 41 | else { 42 | fetchClearance(); 43 | } 44 | } 45 | 46 | var fetchClearance = function() { 47 | _id = ""; 48 | userAgent = ""; 49 | clearance = ""; 50 | 51 | getClearances().then(function(data) { 52 | if (data.length <= 0) { 53 | chrome.storage.local.set({"status": "No clearance in CloudHole API."}); 54 | } else { 55 | var rand = Math.floor(Math.random() * data.length); 56 | userAgent = data[rand].userAgent; 57 | clearance = data[rand].cookies; 58 | _id = data[rand]._id; 59 | chrome.storage.local.set({"status": "Received clearance from CloudHole API."}); 60 | chrome.storage.local.set({"userAgent": userAgent}); 61 | chrome.storage.local.set({"clearance": clearance}); 62 | } 63 | }, function(returnStatus) { 64 | chrome.storage.local.set({"status": `Failed to get clearance from API: ${returnStatus}`}); 65 | }); 66 | } 67 | 68 | var getClearances = function() { 69 | return new Promise(function(resolve, reject) { 70 | var xhr = new XMLHttpRequest(); 71 | xhr.open('GET', clearancesApi + '/clearances', true); 72 | xhr.setRequestHeader('Authorization', apiKey); 73 | xhr.responseType = 'json'; 74 | xhr.onload = function() { 75 | var statusCode = xhr.status; 76 | if (statusCode == 200) { 77 | resolve(xhr.response); 78 | } else { 79 | getSurgeClearances().then(function(data) { 80 | var keyData = []; 81 | for (var i = 0; i < data.length; i++) { 82 | if (apiKey == data[i].key) { 83 | keyData.push(data[i]); 84 | } 85 | } 86 | if (keyData.length > 0) { 87 | resolve(keyData); 88 | } else { 89 | reject("No clearance found."); 90 | } 91 | }, function(errorCode) { 92 | reject(statusCode + " / " + errorCode); 93 | }); 94 | } 95 | }; 96 | xhr.send(); 97 | }); 98 | }; 99 | 100 | var getSurgeClearances = function() { 101 | return new Promise(function(resolve, reject) { 102 | var xhr = new XMLHttpRequest(); 103 | xhr.open('GET', surgeClearances, true); 104 | xhr.responseType = 'json'; 105 | xhr.onload = function() { 106 | var statusCode = xhr.status; 107 | if (statusCode == 200) { 108 | resolve(xhr.response); 109 | } else { 110 | reject(statusCode); 111 | } 112 | }; 113 | xhr.send(); 114 | }); 115 | }; 116 | 117 | var postClearance = function(payload) { 118 | return new Promise(function(resolve, reject) { 119 | var xhr = new XMLHttpRequest(); 120 | xhr.open('POST', clearancesApi + '/clearances'); 121 | xhr.setRequestHeader('Content-Type', 'application/json'); 122 | xhr.setRequestHeader('Authorization', apiKey); 123 | xhr.onload = function() { 124 | var statusCode = xhr.status; 125 | if (statusCode == 201) { 126 | resolve(xhr.response); 127 | } else { 128 | var error = statusCode; 129 | try { 130 | error = JSON.parse(xhr.response).error; 131 | } catch (e) { 132 | error = statusCode; 133 | } 134 | reject(error); 135 | } 136 | }; 137 | xhr.send(JSON.stringify(payload)); 138 | }); 139 | }; 140 | 141 | var deleteClearance = function() { 142 | return new Promise(function(resolve, reject) { 143 | var xhr = new XMLHttpRequest(); 144 | xhr.open('DELETE', clearancesApi + "/clearances/" + _id); 145 | xhr.setRequestHeader('Content-Type', 'application/json'); 146 | xhr.setRequestHeader('Authorization', apiKey); 147 | xhr.onload = function() { 148 | var statusCode = xhr.status; 149 | if (statusCode == 204) { 150 | resolve(xhr.response); 151 | } else { 152 | reject(statusCode); 153 | } 154 | }; 155 | xhr.send(); 156 | }); 157 | }; 158 | 159 | function rewriteHeaders(e) { 160 | // Add Authorization to request when visiting the CloudHole API page 161 | if (e.url.indexOf(clearancesApi) != -1) { 162 | e.requestHeaders.push({ 163 | name: 'Authorization', 164 | value: apiKey 165 | }); 166 | return {requestHeaders: e.requestHeaders}; 167 | } 168 | 169 | // Loop through request headers 170 | for (var header of e.requestHeaders) { 171 | // Continue if not Cookie header or __cfduid not present 172 | if (header.name != "Cookie" || header.value.indexOf("__cfduid") == -1) { 173 | continue; 174 | } 175 | 176 | // Set userAgent to browser-supplied one or ours 177 | if (useBrowserAgent[e.tabId] == true) { 178 | for (var h of e.requestHeaders) { 179 | if (h.name == "User-Agent") { 180 | userAgent = h.value; 181 | } 182 | } 183 | reloaded[e.tabId] = 0; 184 | return {requestHeaders: e.requestHeaders}; 185 | } 186 | else { 187 | for (var h of e.requestHeaders) { 188 | if (h.name == "User-Agent" && userAgent != "") { 189 | h.value = userAgent; 190 | } 191 | } 192 | } 193 | 194 | // Refresh userAgent and clearance if we're seeing a new one, and return headers unchanged 195 | if (clearance == "" && header.value.indexOf("cf_clearance") != -1) { 196 | clearance = header.value.match(/cf_clearance=[a-z0-9\-]+/g)[0]; 197 | 198 | // Set userAgent to current one 199 | for (var h of e.requestHeaders) { 200 | if (h.name == "User-Agent") { 201 | userAgent = h.value; 202 | } 203 | } 204 | chrome.storage.local.set({"userAgent": userAgent}); 205 | chrome.storage.local.set({"clearance": clearance}); 206 | 207 | reloaded[e.tabId] = 0; 208 | sendClearance[e.tabId] = true; 209 | return {requestHeaders: e.requestHeaders}; 210 | } 211 | 212 | // Remove previous __cfduid and cf_clearance 213 | header.value = header.value.replace(/__cfduid=\w+;?\s?/g, ''); 214 | header.value = header.value.replace(/cf_clearance=[a-z0-9\-]+;?\s?/g, '') 215 | 216 | // Add clearance to existing cookies 217 | if (header.value.length > 0 && header.value[header.value.length - 1] != ";") { 218 | header.value += "; "; 219 | } 220 | header.value += clearance; 221 | 222 | reloaded[e.tabId] = 0; 223 | useBrowserAgent[e.tabId] = false; 224 | return {requestHeaders: e.requestHeaders}; 225 | } 226 | } 227 | 228 | function checkHeaders(e) { 229 | if (e.statusCode == 403) { 230 | for (var header of e.responseHeaders) { 231 | if (header.name == "Server" && header.value == "cloudflare-nginx") { 232 | chrome.storage.local.set({"status": "CloudFlared! Need a new clearance cookie..."}); 233 | failing[e.tabId] = true; 234 | 235 | if (useBrowserAgent[e.tabId] == false && useCloudHoleAPI == true && reloaded[e.tabId] > 0) { 236 | deleteClearance().then(function(data) { 237 | chrome.storage.local.set({"status": "Deleted previous clearance from CloudHole API."}); 238 | }, function(returnStatus) { 239 | chrome.storage.local.set({"status": `Failed to delete clearance: ${returnStatus}`}); 240 | }); 241 | useBrowserAgent[e.tabId] = true; 242 | } 243 | 244 | if (sendClearance[e.tabId] == true && reloaded[e.tabId] < 2) { 245 | reloaded[e.tabId] += 1; 246 | chrome.tabs.reload(e.tabId); 247 | } 248 | } 249 | } 250 | } 251 | else { 252 | failing[e.tabId] = false; 253 | useBrowserAgent[e.tabId] = false; 254 | if (sendClearance[e.tabId] == true && useCloudHoleAPI == true) { 255 | sendClearance[e.tabId] = false; 256 | var payload = { 257 | 'userAgent': userAgent, 258 | 'cookies': clearance, 259 | 'label': "WebExt" 260 | }; 261 | postClearance(payload).then(function(data) { 262 | chrome.storage.local.set({"status": "Synced clearance with CloudHole API."}); 263 | if (reloaded[e.tabId] < 2) { 264 | reloaded[e.tabId] += 1; 265 | chrome.tabs.reload(e.tabId); 266 | } 267 | }, function(returnStatus) { 268 | chrome.storage.local.set({"status": `Failed to sync clearance with API: ${returnStatus}`}); 269 | }); 270 | } 271 | } 272 | } 273 | 274 | var getKey = function() { 275 | return new Promise(function(resolve, reject) { 276 | var xhr = new XMLHttpRequest(); 277 | xhr.open('GET', clearancesApi + '/key', true); 278 | xhr.responseType = 'json'; 279 | xhr.onload = function() { 280 | var statusCode = xhr.status; 281 | if (statusCode == 200) { 282 | resolve(xhr.response); 283 | } else { 284 | reject(statusCode); 285 | } 286 | }; 287 | xhr.send(); 288 | }); 289 | }; 290 | 291 | getKey().then(function(data) { 292 | apiKey = data.key; 293 | chrome.storage.local.set({"apiKey": apiKey}); 294 | chrome.storage.local.set({"status": "API key received from CloudHole API."}); 295 | fetchClearance(); 296 | }, function(returnStatus) { 297 | chrome.storage.local.set({"status": `Failed to get API key: ${returnStatus}, using random session key.`}); 298 | fetchClearance(); 299 | }); 300 | 301 | chrome.webRequest.onBeforeSendHeaders.addListener( 302 | rewriteHeaders, 303 | {urls: [""]}, 304 | ["blocking", "requestHeaders"] 305 | ); 306 | 307 | chrome.webRequest.onHeadersReceived.addListener( 308 | checkHeaders, 309 | {urls: [""]}, 310 | ["blocking", "responseHeaders"] 311 | ); 312 | -------------------------------------------------------------------------------- /icons/cloudhole-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scakemyer/cloudhole/f3cfc3eceb63ba8110c30346e475173c1ce8a51c/icons/cloudhole-32.png -------------------------------------------------------------------------------- /icons/cloudhole-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scakemyer/cloudhole/f3cfc3eceb63ba8110c30346e475173c1ce8a51c/icons/cloudhole-48.png -------------------------------------------------------------------------------- /icons/cloudhole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scakemyer/cloudhole/f3cfc3eceb63ba8110c30346e475173c1ce8a51c/icons/cloudhole.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.4.4", 3 | "name": "CloudHole", 4 | "description": "Cross-website CloudFlare cookies", 5 | "manifest_version": 2, 6 | "homepage_url": "https://github.com/scakemyer/cloudhole", 7 | "icons": { 8 | "512": "icons/cloudhole.png", 9 | "48": "icons/cloudhole-48.png" 10 | }, 11 | 12 | "applications": { 13 | "gecko": { 14 | "id": "@cloudhole", 15 | "strict_min_version": "45.0" 16 | } 17 | }, 18 | 19 | "permissions": [ 20 | "tabs", "storage", "webRequest", "webRequestBlocking", "" 21 | ], 22 | 23 | "background": { 24 | "scripts": ["background.js"] 25 | }, 26 | 27 | "browser_action": { 28 | "default_icon": "icons/cloudhole-32.png", 29 | "default_title": "CloudHole settings", 30 | "default_popup": "popup/settings.html" 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /popup/settings.css: -------------------------------------------------------------------------------- 1 | html, body, .settings { 2 | background-color: #ddd; 3 | color: #111; 4 | font-family: "Open Sans", Verdana, Tahoma, "Trebuchet MS", sans-serif; 5 | font-size: 1em; 6 | height: 12em; 7 | width: 40em; 8 | margin: 0; 9 | } 10 | 11 | .settings { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: space-around; 15 | } 16 | 17 | .setting, 18 | .checkbox { 19 | margin: 0.2em; 20 | padding: 0.25em; 21 | } 22 | 23 | .setting span, 24 | .checkbox span { 25 | float: left; 26 | font-size: .8337em; 27 | margin-top: .25em; 28 | } 29 | 30 | input[type=text] { 31 | padding: .25em; 32 | } 33 | 34 | .setting input { 35 | background-color: #111; 36 | border: 0; 37 | border-radius: 5px; 38 | color: #eee; 39 | font-family: Anonymous, Inconsolata, Consolas, "DejaVu Sans Mono", monospace; 40 | width: 36em; 41 | float: right; 42 | } 43 | 44 | .setting input:disabled { 45 | background-color: #333; 46 | } 47 | 48 | button { 49 | background-color: #111; 50 | border: 0; 51 | border-radius: 5px; 52 | color: #eee; 53 | margin-left: .5em; 54 | padding: .25em; 55 | } 56 | 57 | button:active { 58 | background-color: #555; 59 | } 60 | 61 | .checkbox { 62 | float: left; 63 | } 64 | 65 | .right, 66 | .right span { 67 | float: right; 68 | text-align: right; 69 | } 70 | 71 | .checkbox input { 72 | margin-top: .33em; 73 | } 74 | 75 | .status { 76 | bottom: 0; 77 | float: left; 78 | padding: 0.2em; 79 | position: absolute; 80 | } 81 | 82 | .button { 83 | padding: 0.2em; 84 | } 85 | 86 | .button button { 87 | float: right; 88 | } 89 | -------------------------------------------------------------------------------- /popup/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CloudHole settings 8 | 9 | 10 | 11 |
12 |
UserAgent:
13 |
Clearance:
14 |
API key:
15 |
16 |
 
17 |
 
18 |
19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /popup/settings.js: -------------------------------------------------------------------------------- 1 | 2 | var clearancesApi = "https://cloudhole.herokuapp.com"; 3 | var surgeClearances = "https://cloudhole.surge.sh/cloudhole.json"; 4 | 5 | var refresh = function() { 6 | chrome.storage.local.get("apiKey", function(result) { 7 | document.querySelector("#apikey").value = result.apiKey ? result.apiKey : ""; 8 | }); 9 | chrome.storage.local.get("userAgent", function(result) { 10 | document.querySelector("#useragent").value = result.userAgent ? result.userAgent : ""; 11 | }); 12 | chrome.storage.local.get("clearance", function(result) { 13 | document.querySelector("#clearance").value = result.clearance ? result.clearance : ""; 14 | }); 15 | chrome.storage.local.get("useCloudHoleAPI", function(result) { 16 | document.querySelector("#cloudholeapi").checked = result.useCloudHoleAPI === false ? false : "checked"; 17 | }); 18 | chrome.storage.local.get("status", function(result) { 19 | document.querySelector("#status").innerText = result.status ? result.status : ""; 20 | }); 21 | } 22 | refresh(); 23 | 24 | var getKey = function() { 25 | return new Promise(function(resolve, reject) { 26 | var xhr = new XMLHttpRequest(); 27 | xhr.open('GET', clearancesApi + '/key', true); 28 | xhr.responseType = 'json'; 29 | xhr.onload = function() { 30 | var statusCode = xhr.status; 31 | if (statusCode == 200) { 32 | resolve(xhr.response); 33 | } else { 34 | reject(statusCode); 35 | } 36 | }; 37 | xhr.send(); 38 | }); 39 | }; 40 | 41 | var fetchClearance = function() { 42 | chrome.storage.local.set({"status": ""}, refresh); 43 | 44 | getClearances().then(function(data) { 45 | if (data.length <= 0) { 46 | chrome.storage.local.set({"status": "No clearance in CloudHole API."}, refresh); 47 | } else { 48 | var rand = Math.floor(Math.random() * data.length); 49 | chrome.storage.local.set({"userAgent": data[rand].userAgent}); 50 | chrome.storage.local.set({"clearance": data[rand].cookies}); 51 | chrome.storage.local.set({"status": "Received clearance from CloudHole API."}, refresh); 52 | } 53 | }, function(returnStatus) { 54 | chrome.storage.local.set({"status": `Failed to get clearance from API: ${returnStatus}`}, refresh); 55 | }); 56 | } 57 | 58 | var getClearances = function() { 59 | return new Promise(function(resolve, reject) { 60 | var xhr = new XMLHttpRequest(); 61 | xhr.open('GET', clearancesApi + '/clearances', true); 62 | xhr.setRequestHeader('Authorization', document.querySelector("#apikey").value); 63 | xhr.responseType = 'json'; 64 | xhr.onload = function() { 65 | var statusCode = xhr.status; 66 | if (statusCode == 200) { 67 | resolve(xhr.response); 68 | } else { 69 | getSurgeClearances().then(function(data) { 70 | var keyData = []; 71 | for (var i = 0; i < data.length; i++) { 72 | if (apiKey == data[i].key) { 73 | keyData.push(data[i]); 74 | } 75 | } 76 | if (keyData.length > 0) { 77 | resolve(keyData); 78 | } else { 79 | reject("No clearance found."); 80 | } 81 | }, function(errorCode) { 82 | reject(statusCode + " / " + errorCode); 83 | }); 84 | } 85 | }; 86 | xhr.send(); 87 | }); 88 | }; 89 | 90 | var getSurgeClearances = function() { 91 | return new Promise(function(resolve, reject) { 92 | var xhr = new XMLHttpRequest(); 93 | xhr.open('GET', surgeClearances, true); 94 | xhr.responseType = 'json'; 95 | xhr.onload = function() { 96 | var statusCode = xhr.status; 97 | if (statusCode == 200) { 98 | resolve(xhr.response); 99 | } else { 100 | reject(statusCode); 101 | } 102 | }; 103 | xhr.send(); 104 | }); 105 | }; 106 | 107 | var postClearance = function(payload) { 108 | return new Promise(function(resolve, reject) { 109 | var xhr = new XMLHttpRequest(); 110 | xhr.open('POST', clearancesApi + '/clearances'); 111 | xhr.setRequestHeader('Content-Type', 'application/json'); 112 | xhr.setRequestHeader('Authorization', document.querySelector("#apikey").value); 113 | xhr.onload = function() { 114 | var statusCode = xhr.status; 115 | if (statusCode == 201) { 116 | resolve(xhr.response); 117 | } else { 118 | var error = statusCode; 119 | try { 120 | error = JSON.parse(xhr.response).error; 121 | } catch (e) { 122 | error = statusCode; 123 | } 124 | reject(error); 125 | } 126 | }; 127 | xhr.send(JSON.stringify(payload)); 128 | }); 129 | }; 130 | 131 | document.querySelector("#save").addEventListener("click", function() { 132 | chrome.storage.local.set({"status": ""}, refresh); 133 | 134 | var userAgent = document.querySelector("#useragent").value; 135 | var clearance = document.querySelector("#clearance").value; 136 | var apiKey = document.querySelector("#apikey").value; 137 | var useCloudHoleAPI = document.querySelector("#cloudholeapi").checked; 138 | 139 | chrome.storage.local.set({"userAgent": userAgent}); 140 | chrome.storage.local.set({"clearance": clearance}); 141 | 142 | if (apiKey != "") { 143 | chrome.storage.local.set({"apiKey": apiKey}); 144 | } 145 | chrome.storage.local.get('useCloudHoleAPI', function(result) { 146 | if (useCloudHoleAPI != result.useCloudHoleAPI) { 147 | chrome.storage.local.set({"useCloudHoleAPI": useCloudHoleAPI}); 148 | } 149 | }); 150 | 151 | var payload = { 152 | 'userAgent': userAgent, 153 | 'cookies': clearance, 154 | 'label': "WebExtSave" 155 | }; 156 | 157 | postClearance(payload).then(function(data) { 158 | chrome.storage.local.set({"status": "Saved clearance to CloudHole API."}, refresh); 159 | }, function(returnStatus) { 160 | chrome.storage.local.set({"status": `Save failed: ${returnStatus}`}, refresh); 161 | }); 162 | }); 163 | 164 | document.querySelector("#refresh").addEventListener("click", function() { 165 | chrome.storage.local.set({"status": ""}, refresh); 166 | 167 | chrome.storage.local.get('useCloudHoleAPI', function(result) { 168 | if (result.useCloudHoleAPI == true) { 169 | getKey().then(function(data) { 170 | chrome.storage.local.get('apiKey', function(result) { 171 | if (result.apiKey != data.key) { 172 | chrome.storage.local.set({"apiKey": data.key}); 173 | chrome.storage.local.set({"status": "New API key received from CloudHole API."}, refresh); 174 | document.querySelector("#apikey").value = data.key; 175 | } 176 | fetchClearance(); 177 | }); 178 | }, function(returnStatus) { 179 | chrome.storage.local.set({"status": `Failed to get API key: ${returnStatus}`}, refresh); 180 | fetchClearance(); 181 | }); 182 | } 183 | }); 184 | }); 185 | 186 | document.querySelector("#browseragent").addEventListener("click", function() { 187 | if (document.querySelector("#browseragent").checked == true) { 188 | document.querySelector("#useragent").value = navigator.userAgent; 189 | document.querySelector("#clearance").value = ""; 190 | document.querySelector("#status").innerText = "Click Save to apply changes."; 191 | } else { 192 | chrome.storage.local.set({"status": "Using current UserAgent and clearance."}, refresh); 193 | } 194 | }); 195 | --------------------------------------------------------------------------------