Tracking Proxy Helper v1.0

This commit is contained in:
Andi Petzoldt 2024-07-05 10:08:23 +02:00
parent 53fcdc02c1
commit 97e1ab9232
14 changed files with 1198 additions and 1 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
tmp/
desktop.ini
Thumbs.db
.DS_Store
.vscode
*.sublime-project
*.sublime-workspace
.obsidian
.idea
.trash

View File

@ -1,2 +1,54 @@
# tracking-proxy-helper
# Tracking Proxy Helper
## Overview
The "Tracking Proxy Helper" Chrome Extension provides various functions to manage and monitor Tracking Proxy events. This extension allows setting HTTP headers, injecting JavaScript, and tracking events in real-time.
## Installation
1. **Download the project files** or clone the repository.
2. **Open Google Chrome** and navigate to `chrome://extensions/`.
3. **Enable Developer mode** (top right).
4. **Click on "Load unpacked"** and select the folder containing the downloaded files.
## Usage
### Popup
The popup window displays the current status of the Tracking Proxy and lists all captured events.
- **Account ID:** Displays the current Tracking Proxy ID.
- **Events:** Shows a list of events captured by the Tracking Proxy.
### Options
Configure the extension settings through the options page.
1. **HTTP Header for Dev Environment:**
- Set the HTTP header name and value.
- Add URLs to match for applying the header, with an option to use regex.
2. **JavaScript Injection and Blocking:**
- Specify a URL for JavaScript injection and blocking.
- Enter the JavaScript content to inject.
- Options to block Google Tag Manager and Tracking Proxy code based on specified criteria.
### Scripts and Files
- **content.js:** Injects scripts into the web page and handles messaging between the extension and the web page.
- **inject-listener.js:** Listens for specific messages to manage the Tracking Proxy.
- **inject-userscript.js:** Injects user scripts into the web page.
- **popup.html:** HTML structure for the extension's popup interface.
- **popup.js:** JavaScript to handle the logic for the popup interface.
- **options.html:** HTML structure for the extension's options page.
- **options.js:** JavaScript to handle the logic for the options page.
- **worker.js:** Service worker to manage background tasks and event tracking.
- **manifest.json:** Defines the extension's metadata, permissions, and configurations.
## Contributing
Feel free to contribute by submitting issues or pull requests. For significant changes, please open an issue first to discuss what you would like to change.
## License
This project is licensed under the MIT License.

90
content.js Normal file
View File

@ -0,0 +1,90 @@
/***** Content JS for Chrome Extension: Tracking Proxy Helper *****/
/*** Initialisation ***/
let tpdm = { activeTabId: null, activeWindowId: null, extInit: false, tpl: { tpId: '', eventCount: 0 } };
tpdm.data = tpdm.data || JSON.parse(JSON.stringify(tpdm.tpl));
//console.log('content.js loaded');
/*** Logic ***/
// Function to inject javascript code into the Page DOM
// --> Accessing window object see: https://silverbirder.github.io/en-US/blog/contents/chrome_extension_development_feedback/
const injectScript = (filePath, id, tag) => {
if (!document.getElementById(id)) {
var node = document.getElementsByTagName(tag)[0];
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", filePath);
script.setAttribute("id", id);
node.appendChild(script);
}
};
// Listener to receive messages from the injected script
window.addEventListener('message', function(event) {
//console.log('Message received from injected script:', event);
if (event.data.type === 'CHECK_TPT_RESULT') {
//console.log('Message received from injected script:', event);
chrome.runtime.sendMessage({ type: 'CHECK_TPT', hasTPT: event.data.hasTPT, tpobj:event.data.tpobj });
}
if (event.data.type === 'TRACKING_PROXY_RESPONSE') {
//console.log('Message received from injected TP script:', event);
const eventData = event.data.details.eventData;
const metaData = event.data.details.metaData;
chrome.runtime.sendMessage({ type: 'TRACKING_PROXY_EVENT', details: { eventData:eventData, metaData:metaData } });
}
});
// Listener for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'TRACKING_PROXY_REQUEST') {
window.postMessage({ type: 'TRACKING_PROXY_REDIRECT', details: message.details }, '*');
}
});
// Inject script on document load
if (document.readyState === 'complete' || document.readyState === 'interactive') {
injectScript(chrome.runtime.getURL("inject-listener.js"), "tp_dev_mode_script", "head");
} else {
document.addEventListener('DOMContentLoaded', function() {
injectScript(chrome.runtime.getURL("inject-listener.js"), "tp_dev_mode_script", "head");
});
}
chrome.storage.sync.get(['scriptContent', 'scriptUrl', 'scriptUrlIsRegex'], function(data) {
const scriptContent = data.scriptContent || '';
const scriptUrl = data.scriptUrl || '';
const scriptUrlIsRegex = data.scriptUrlIsRegex || false;
if (!scriptUrl || !scriptContent) {
//console.log('No script URL or script content defined, no script injected');
return;
}
let urlMatches;
if (scriptUrlIsRegex) {
const regex = new RegExp(scriptUrl);
urlMatches = regex.test(window.location.href);
} else {
urlMatches = window.location.href.includes(scriptUrl);
}
if (urlMatches) {
if (!document.getElementById('tp_user_script')) {
//console.log(`Injecting script into URL: ${window.location.href}`);
const script = document.createElement('script');
script.src = chrome.runtime.getURL('inject-userscript.js');
script.setAttribute("id", "tp_user_script");
(document.head || document.documentElement).appendChild(script);
script.onload = function() {
window.postMessage({ type: 'INJECT_USER_SCRIPT', scriptContent: scriptContent }, '*');
script.remove();
};
}
} else {
//console.log(`URL does not match, no script injected for URL ${window.location.href}`);
}
});
// EOF

BIN
img/icon_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

110
inject-listener.js Normal file
View File

@ -0,0 +1,110 @@
// inject-listener.js
//console.log('Injected listener loaded');
window.addEventListener('message', function(event) {
if (!event.data || !event.data.type || !event.data.details) return;
//console.log('TRACKING_PROXY_REQUEST message 1', event.data);
if (event.data.type === 'TRACKING_PROXY_REDIRECT') {
//console.log('TRACKING_PROXY_REQUEST message 2', event);
const eventData = event.data.details.eventData;
const metaData = event.data.details.metaData;
if (eventData.cid && window.__tpt && eventData.cid === window.__tpt.cid) {
//console.log('Tracking Proxy Event detected:', event);
window.postMessage({ type: 'TRACKING_PROXY_RESPONSE', details: { eventData:eventData, metaData:metaData } }, '*');
} else {
//console.log('No matching cid found in request body', details);
}
}
}, false);
// Function to post messages
function sendTPmsg(o){
window.postMessage(o, '*');
}
function getTPDetails() {
return window.__tpt ? JSON.parse(JSON.stringify(window.__tpt)) : {};
}
// Function to check for TP and send message if available
function checkTPT() {
const tpDetails = getTPDetails();
sendTPmsg({ type: 'CHECK_TPT_RESULT', hasTPT: !!tpDetails.cid, tpobj: tpDetails });
}
// Proxy handler to detect changes
const handler = {
set(target, property, value) {
target[property] = value;
//console.log(`Property ${property} changed to`, value);
if (property === 'cid') {
checkTPT();
}
return true;
},
get(target, property) {
return target[property];
}
};
// Wrap the __tpt object with a Proxy
if (window.__tpt) {
window.__tpt = new Proxy(window.__tpt, handler);
} else {
Object.defineProperty(window, '__tpt', {
set(value) {
if (value && typeof value === 'object') {
value = new Proxy(value, handler);
}
Object.defineProperty(this, '__tpt', { value, writable: true, configurable: true, enumerable: true });
checkTPT();
},
configurable: true,
enumerable: true
});
}
function exposeTPT() {
if (!window.__tpt) {
//console.log('No __tpt object found.');
return;
}
try {
// Deep clone the object to break references
const tpDetails = JSON.parse(JSON.stringify(window.__tpt));
//console.log('Current TP details:', tpDetails);
} catch (error) {
//console.error('Error exposing __tpt details:', error);
}
}
// Expose the function to global scope for easy access from the console
window.exposeTPT = exposeTPT;
/*
(function() {
let tpid = '';
//console.log("inject.js loaded");
function checkTPT() {
if (!window.__tpt) return;
let tpobj = JSON.parse(JSON.stringify(window.__tpt));
console.log('checkTPT result:', tpobj);
if (!tpobj.cid) return;
window.postMessage({ type: 'CHECK_TPT_RESULT', tpobj:tpobj }, '*');
}
// Überprüfe sofort
checkTPT();
// Wiederhole die Überprüfung nach einer kurzen Verzögerung, um sicherzustellen, dass das Objekt geladen ist
const observer = new MutationObserver((mutations, observer) => {
if (window.__tpt && window.__tpt.cid) {
//console.log('TP is now available');
checkTPT();
observer.disconnect();
}
});
// Starten des MutationObserver
if (!window.__tpt && !window.__tpt.cid) observer.observe(document.documentElement, { childList: true, subtree: true });
//console.log('injected listener executed');
})();
*/

15
inject-userscript.js Normal file
View File

@ -0,0 +1,15 @@
// inject-userscript.js
//console.log('Injected userscript executed');
window.addEventListener('message', function(event) {
if (event.source !== window || !event.data || event.data.type !== 'INJECT_USER_SCRIPT') return;
const scriptContent = event.data.scriptContent;
if (scriptContent) {
const script = document.createElement('script');
script.textContent = scriptContent;
(document.head || document.documentElement).appendChild(script);
script.onload = function () {
script.remove();
};
}
}, false);

73
manifest.json Normal file
View File

@ -0,0 +1,73 @@
/***** Manifest for Chrome Extension: Tracking Proxy Helper *****/
// Doku Manifest: https://developer.chrome.com/docs/extensions/reference/manifest?hl=de
// Doku Extension API: https://developer.chrome.com/docs/extensions/reference/api/?hl=de
// Very helpful: https://silverbirder.github.io/en-US/blog/contents/chrome_extension_development_feedback/
// Example Extensions: https://developer.chrome.com/docs/extensions/samples?hl=de
{
"manifest_version": 3,
"name": "Tracking Proxy Helper",
"author": "andi@tracking-garden.com",
"version": "1.0",
"description": "This extension gives some information and options using the Tracking Proxy.",
"icons": {
//"16": "/img/get_started16.png",
//"32": "/img/get_started32.png",
"48": "/img/icon_48.png"
//"128": "/img/get_started128.png"
},
//"default_locale": "en_US",
"background": {
"service_worker": "worker.js"
},
//"externally_connectable": {
// "matches": ["*://*/*"]
//},
"permissions": [
"activeTab",
"declarativeNetRequest",
"declarativeNetRequestFeedback",
"declarativeNetRequestWithHostAccess",
"scripting",
"storage",
"tabs",
"webNavigation",
"webRequest",
//"webRequestBlocking",
"userScripts"
],
//"optional_permissions": ["topSites"],
//"content_security_policy": {
// "extension_pages": "default-src 'self' 'wasm-unsafe-eval'"
//}
"host_permissions": [
"*://*/*"
],
"content_scripts": [
{
"matches": ["*://*/*"],
//"matches": ["<all_urls>"],
"js": ["content.js"/*,"inject.js"*/]
}
],
"web_accessible_resources": [
{
"matches": ["*://*/*"],
"resources": ["inject-listener.js","inject-userscript.js"]
}
],
"action": {
"default_popup": "popup.html",
"default_title": "Tracking Proxy - Dev Mode",
"default_icon": {
//"16": "/img/get_started16.png",
//"32": "/img/get_started32.png",
"48": "/img/icon_48.png"
//"128": "/img/get_started128.png"
}
},
"options_page": "options.html"
}
// EOF

102
options.css Normal file
View File

@ -0,0 +1,102 @@
/***** Options CSS for Chrome Extension: Tracking Proxy Helper *****/
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #382d3d; /* Hintergrundfarbe der Seite */
color: white; /* Allgemeine Textfarbe */
}
h1 {
font-size: 24px;
margin-bottom: 20px;
color: #fe6845; /* Farbe für H1 Überschriften */
}
h2 {
font-size: 20px;
margin-top: 20px;
color: #382d3d; /* Farbe für H2 Überschriften */
}
h3 {
font-size: 20px;
margin-top: 20px;
color: white; /* Farbe für H3 Überschriften */
}
.section {
background-color: #eeeeee; /* Hintergrundfarbe der Kästen */
border: 1px solid #000000;
padding: 20px;
margin-bottom: 20px;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #382d3d; /* Farbe der Labels */
}
input[type="text"],
textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
background-color: #fff; /* Hintergrund der Eingabefelder */
color: #000; /* Textfarbe in Eingabefeldern */
border: 1px solid #ccc; /* Rahmen der Eingabefelder */
}
.url-field {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.url-input {
flex: 1;
}
.regex-checkbox {
margin-left: 10px;
}
.inline-checkbox {
display: inline-block;
margin-left: 10px;
white-space: nowrap;
}
button {
padding: 10px 15px;
background-color: #fe6845;
color: white;
border: none;
cursor: pointer;
}
button.remove-url-field {
background-color: #382d3d;
margin-left: 10px;
}
button#add-url-field {
background-color: #382d3d;
margin-top: 10px;
}
.url-match-label {
display: block;
margin-bottom: 10px;
margin-top: 15px;
color: #382d3d; /* Farbe für URL Match Labels */
}
/* EOF */

82
options.html Normal file
View File

@ -0,0 +1,82 @@
<!----- Options HTML for Chrome Extension: Tracking Proxy Helper ----->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tracking Proxy Helper - Extension Options</title>
<link rel="stylesheet" href="options.css">
</head>
<body>
<h1>Tracking Proxy Helper Settings</h1>
<form id="options-form">
<div class="section" id="httpheader">
<h2>HTTP Header for Dev.Environment</h2>
<p class="url-match-label">Notice: The HTTP Header Name is: <strong>Tp-Dev</strong></p>
<div class="form-group">
<label for="header-value">HTTP Header Value:</label>
<input type="text" id="header-value" name="header-value">
</div>
<strong class="url-match-label">URLs to match:</strong>
<div id="url-fields-container">
<div class="url-field">
<input type="text" class="url-input" name="url[]">
<label>
<input type="checkbox" class="regex-checkbox"> Is Regex
</label>
<button type="button" class="remove-url-field">Remove</button>
</div>
</div>
<button type="button" id="add-url-field">Add URL Field</button>
</div>
<div class="section" id="blocking">
<h2>JavaScript Injection and Blocking</h2>
<div class="form-group">
<label for="script-url">URL for JavaScript Injection and Blocking:</label>
<div class="url-field">
<input type="text" id="script-url" name="script-url">
<label class="inline-checkbox">
<input type="checkbox" id="script-url-regex"> Is Regex
</label>
</div>
</div>
<div class="form-group">
<label for="script-content">JavaScript to Inject:</label>
<textarea id="script-content" name="script-content" rows="4"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="block-gtm"> Block Google Tag Manager
</label>
</div>
<div class="form-group">
<label for="gtm-container-id">Google Tag Manager Container ID:</label>
<div class="url-field">
<input type="text" id="gtm-container-id" name="gtm-container-id">
<label class="inline-checkbox">
<input type="checkbox" id="gtm-container-id-regex"> Is Regex
</label>
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="block-tracking-proxy"> Block Tracking Proxy Code
</label>
</div>
<div class="form-group">
<label for="tracking-proxy-url">URL for Tracking Proxy:</label>
<div class="url-field">
<input type="text" id="tracking-proxy-url" name="tracking-proxy-url">
<label class="inline-checkbox">
<input type="checkbox" id="tracking-proxy-url-regex"> Is Regex
</label>
</div>
</div>
</div>
<button type="submit">Save</button>
</form>
<h3>Saved Values</h3>
<pre id="saved-values"></pre>
<script src="options.js"></script>
</body>
</html>

151
options.js Normal file
View File

@ -0,0 +1,151 @@
/***** Options JS for Chrome Extension: Tracking Proxy Helper *****/
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('options-form');
const addUrlButton = document.getElementById('add-url-field');
const urlFieldsContainer = document.getElementById('url-fields-container');
const savedValuesContainer = document.getElementById('saved-values');
// Add event listener for adding URL fields
addUrlButton.addEventListener('click', function () {
const urlField = createUrlField();
urlFieldsContainer.appendChild(urlField);
});
// Load saved options
loadOptions();
// Save options on form submit
form.addEventListener('submit', function (event) {
event.preventDefault();
saveOptions();
});
});
/**
* Create a new URL field element
* @returns {HTMLDivElement} The created URL field element
*/
function createUrlField() {
const urlField = document.createElement('div');
urlField.className = 'url-field';
urlField.innerHTML = `
<input type="text" class="url-input" name="url[]">
<label>
<input type="checkbox" class="regex-checkbox"> Is Regex
</label>
<button type="button" class="remove-url-field">Remove</button>
`;
// Add event listener for removing URL fields
urlField.querySelector('.remove-url-field').addEventListener('click', function () {
urlField.remove();
});
return urlField;
}
/**
* Save options to chrome.storage.sync
*/
function saveOptions() {
const headerValue = document.getElementById('header-value').value;
const urlFields = document.querySelectorAll('#httpheader .url-field'); // Only URL fields
const urls = Array.from(urlFields).map(urlField => {
const urlInput = urlField.querySelector('.url-input');
const regexCheckbox = urlField.querySelector('.regex-checkbox');
// Ensure that the inputs exist
if (!urlInput || !regexCheckbox) {
console.error('Invalid URL field:', urlField);
return null;
}
return {
url: urlInput.value,
isRegex: regexCheckbox.checked
};
}).filter(urlObj => urlObj !== null); // Remove null entries
const scriptContent = document.getElementById('script-content').value;
const scriptUrl = document.getElementById('script-url').value;
const scriptUrlIsRegex = document.getElementById('script-url-regex').checked;
const blockGtm = document.getElementById('block-gtm').checked;
const gtmContainerId = document.getElementById('gtm-container-id').value;
const gtmContainerIdIsRegex = document.getElementById('gtm-container-id-regex').checked;
const trackingProxyUrl = document.getElementById('tracking-proxy-url').value;
const trackingProxyUrlIsRegex = document.getElementById('tracking-proxy-url-regex').checked;
const blockTrackingProxy = document.getElementById('block-tracking-proxy').checked;
chrome.storage.sync.set({
headerValue,
urls,
scriptContent,
scriptUrl,
scriptUrlIsRegex,
blockGtm,
gtmContainerId,
gtmContainerIdIsRegex,
trackingProxyUrl,
trackingProxyUrlIsRegex,
blockTrackingProxy
}, function () {
alert('Options saved!');
displaySavedValues(); // Update displayed saved values
});
}
/**
* Load options from chrome.storage.sync
*/
function loadOptions() {
chrome.storage.sync.get([
'headerValue',
'urls',
'scriptContent',
'scriptUrl',
'scriptUrlIsRegex',
'blockGtm',
'gtmContainerId',
'gtmContainerIdIsRegex',
'trackingProxyUrl',
'trackingProxyUrlIsRegex',
'blockTrackingProxy'
], function (data) {
document.getElementById('header-value').value = data.headerValue || '';
const urlFieldsContainer = document.getElementById('url-fields-container');
urlFieldsContainer.innerHTML = '';
(data.urls || []).forEach(urlObj => {
const urlField = createUrlField();
urlField.querySelector('.url-input').value = urlObj.url;
urlField.querySelector('.regex-checkbox').checked = urlObj.isRegex;
urlFieldsContainer.appendChild(urlField);
});
document.getElementById('script-content').value = data.scriptContent || '';
document.getElementById('script-url').value = data.scriptUrl || '';
document.getElementById('script-url-regex').checked = data.scriptUrlIsRegex || false;
document.getElementById('block-gtm').checked = data.blockGtm || false;
document.getElementById('gtm-container-id').value = data.gtmContainerId || '';
document.getElementById('gtm-container-id-regex').checked = data.gtmContainerIdIsRegex || false;
document.getElementById('tracking-proxy-url').value = data.trackingProxyUrl || '';
document.getElementById('tracking-proxy-url-regex').checked = data.trackingProxyUrlIsRegex || false;
document.getElementById('block-tracking-proxy').checked = data.blockTrackingProxy || false;
displaySavedValues(); // Display saved values on load
});
}
/**
* Display saved values in a <pre> element
*/
function displaySavedValues() {
chrome.storage.sync.get(null, function(items) {
const savedValuesContainer = document.getElementById('saved-values');
savedValuesContainer.textContent = JSON.stringify(items, null, 2);
});
}
// EOF

114
popup.css Normal file
View File

@ -0,0 +1,114 @@
body {
font-family: Arial, sans-serif;
background-color: #382d3d;
color: #ffffff;
margin: 0;
padding: 2px;
}
/* Stil für den Event-Kasten */
details {
background-color: #eeeeee; /* Helles Grau für den zusammengeklappten Zustand */
margin-bottom: 5px; /* Abstand zwischen den Event-Kästen */
border-radius: 5px; /* Abgerundete Ecken */
padding: 5px; /* Innenabstand innerhalb des details Elements */
border: 1px solid #ccc; /* Leichter Rahmen um das Element */
}
/* Stil für den aufklappbaren Teil des Event-Kastens */
summary {
font-weight: bold;
color: #382d3d; /* Dunkle Schrift für den Event-Titel */
cursor: pointer;
}
pre {
background-color: #ffffff; /* Weißer Hintergrund für JSON-Inhalte */
color: #382d3d; /* Dunkle Schriftfarbe für JSON-Inhalte */
border: none; /* Entfernt jeglichen Rahmen */
overflow-x: auto; /* Ermöglicht horizontales Scrollen bei langen Zeilen */
white-space: pre-wrap; /* Ermöglicht den Zeilenumbruch bei Bedarf */
padding: 5px; /* Innenabstand für bessere Lesbarkeit */
}
/* Farben und Stil für die Eigenschaftsnamen innerhalb des JSON-Objekts */
.json-property-name {
color: #fe6845; /* Farbe für die Eigenschaftsnamen */
font-weight: bold;
}
/* Stil für die Werte innerhalb des JSON-Objekts, wenn benötigt */
.json-property-value {
color: #382d3d;
}
.container {
max-width: 600px;
margin: auto;
background: #382d3d;
padding: 5px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-width: 300px;
}
.label {
font-weight: bold;
color: #fe6845;
}
.value {
color: #ffffff;
}
.events {
margin-top: 10px;
}
.event-details {
color: #382d3d;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.event-details summary {
cursor: pointer;
font-weight: bold;
color: #382d3d; /* Dunkelviolett für Event-Namen */
}
.event-details pre {
background: #fff;
color: #382d3d;
padding: 10px;
border-radius: 4px;
overflow: auto;
max-height: 200px;
font-family: monospace; /* Schriftart für bessere Lesbarkeit von JSON */
}
.event-details pre .key {
color: #fe6845; /* Orange für JSON-Schlüssel/Eigenschaftsnamen */
}
.event-details pre .string, .event-details pre .number {
color: #382d3d; /* Dunkelviolett für Werte im JSON */
}
#options-link {
position: absolute; /* Absolut positioniert, relativ zum nächstgelegenen positionierten Vorfahren */
top: 5px; /* Abstand von oben */
right: 5px; /* Abstand von rechts */
color: #ccc7aa; /* Linkfarbe */
padding: 5px 5px;
border-radius: 5px;
text-decoration: none;
font-weight: normal; /* Entfernt fettgedrucktes Styling */
font-size: 10px;
}
#options-link:hover {
background-color: #fe6845;
color: #382d3d;
}

20
popup.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tracking Proxy Helper</title>
<link rel="stylesheet" href="popup.css">
<script src="popup.js" defer></script>
</head>
<body>
<a href="#" id="options-link">Settings</a>
<div class="container">
<p><span class="label">Account ID:</span> <span id="tp-id" class="value">No Tracking Proxy detected</span></p>
<p>
<span class="label">Events:</span>
<div id="events" class="events"></div>
</p>
</div>
</body>
</html>

55
popup.js Normal file
View File

@ -0,0 +1,55 @@
/***** Popup JS for Chrome Extension: Tracking Proxy Helper *****/
//function test(o){
// chrome.runtime.sendMessage({ type: 'REQUEST_POPUP_DATA2',details:typeof o=='object'?JSON.parse(JSON.stringify(o)):null,test:123 });
//}
function syntaxHighlight(json) {
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[\dA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
let cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key'; // Anwenden der CSS-Klasse 'key' auf JSON Schlüssel
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
// Initialize popup with Tracking Proxy Events
document.addEventListener('DOMContentLoaded', function() {
const tpIdElement = document.getElementById('tp-id');
const eventsElement = document.getElementById('events');
chrome.runtime.sendMessage({ type: 'REQUEST_POPUP_DATA' }, function(response) {
//test(response);
if (typeof response=='object') {
const tpId = response.tpId || 'Not Available';
tpIdElement.textContent = tpId;
const events = response.events || [];
eventsElement.innerHTML = events.map((event, index) => `
<details class="event-details">
<summary>${event.event}</summary>
<pre>${syntaxHighlight(JSON.stringify(event, null, 2))}</pre>
</details>
`).join('');
}
});
});
document.getElementById('options-link').addEventListener('click', function(e) {
e.preventDefault();
// Öffnet die Options-Seite
if (chrome.runtime.openOptionsPage) {
chrome.runtime.openOptionsPage();
} else {
window.open(chrome.runtime.getURL('options.html'));
}
});

323
worker.js Normal file
View File

@ -0,0 +1,323 @@
/***** Service Worker JS for Chrome Extension: Tracking Proxy Helper *****/
/*** Initialisation ***/
let tpdm = { activeTabId: null, activeWindowId: null, extInit: false, tpl: { tpId:'', eventCount:0, events:[] } };
//tpdm.data = tpdm.data || JSON.parse(JSON.stringify(tpdm.tpl));
tpdm.data = tpdm.data || {};
//console.log('Worker loaded');
/*** Helper Function Library ***/
/**
* Function to Update the Badge Text
*/
function updateBadge(text, tabId) {
chrome.action.setBadgeBackgroundColor({ color: '#fe6845', tabId: tabId });
chrome.action.setBadgeTextColor({ color: '#ffffff', tabId: tabId });
chrome.action.setBadgeText({ text: text.toString(), tabId: tabId });
}
/**
* Function to Clear the Badge
*/
function clearBadge(tabId) {
chrome.action.setBadgeText({ text: '', tabId: tabId });
}
/**
* Function to Reset the Badge Text
*/
function resetBadge(tabId) {
if (typeof tpdm.data[tabId]!='object') tpdm.data[tabId] = JSON.parse(JSON.stringify(tpdm.tpl));
tpdm.data[tabId] = JSON.parse(JSON.stringify(tpdm.tpl));
clearBadge(tabId);
}
/**
* Function to Increment Badge Counter
*/
function incrementBadge(tabId) {
if (typeof tpdm.data[tabId]!='object') tpdm.data[tabId] = JSON.parse(JSON.stringify(tpdm.tpl));
tpdm.data[tabId].eventCount += 1;
updateBadge(tpdm.data[tabId].eventCount, tabId);
}
/**
* Function to get the current active tab ID
*/
function getCurrentTabId(callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
if (tabs.length > 0) {
callback(tabs[0].id);
} else {
//console.error('No active tab found');
}
});
}
/**
* Load options from storage and set declarativeNetRequest rules
*/
function loadOptionsAndSetRules() {
console.log('Tracking Proxy Helper installed');
chrome.storage.sync.get([
'headerValue', 'urls', 'blockGtm', 'gtmContainerId', 'gtmContainerIdIsRegex',
'trackingProxyUrl', 'trackingProxyUrlIsRegex', 'blockTrackingProxy', 'scriptUrl', 'scriptUrlIsRegex'
], function(data) {
const headerValue = data.headerValue || '';
const urls = data.urls || [];
const blockGtm = data.blockGtm || false;
const gtmContainerId = data.gtmContainerId || '';
const gtmContainerIdIsRegex = data.gtmContainerIdIsRegex || false;
const trackingProxyUrl = data.trackingProxyUrl || '';
const trackingProxyUrlIsRegex = data.trackingProxyUrlIsRegex || false;
const blockTrackingProxy = data.blockTrackingProxy || false;
const scriptUrl = data.scriptUrl || '';
const scriptUrlIsRegex = data.scriptUrlIsRegex || false;
// If no header value or no URLs are defined, do not set any rules
if (!headerValue || urls.length === 0) {
//console.log('No header value or URLs defined, no rules set');
return;
}
// Create conditions based on the URLs and regex
const conditions = urls
.filter(urlObj => urlObj.url) // Filter out empty URLs
.map((urlObj, index) => {
const condition = {
id: index + 1,
priority: 1,
action: {
type: "modifyHeaders",
requestHeaders: [
{ header: "Tp-Dev", operation: "set", value: headerValue }
]
},
condition: {
regexFilter: urlObj.isRegex ? urlObj.url : `^${urlObj.url}$`,
isUrlFilterCaseSensitive: false,
//resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]
}
};
console.log('Condition created:', condition);
return condition;
});
const rules = conditions.map((condition, index) => ({
...condition,
id: index + 1
}));
const scriptUrlMatches = (url) => {
if (!scriptUrl) return false;
if (scriptUrlIsRegex) {
const regex = new RegExp(scriptUrl);
return regex.test(url);
}
return url.includes(scriptUrl);
};
// Add GTM blocking rule if blockGtm is enabled and scriptUrl matches
if (blockGtm && gtmContainerId && scriptUrlMatches(scriptUrl)) {
const gtmUrl = gtmContainerIdIsRegex ? gtmContainerId : `https://www.googletagmanager.com/gtm.js?id=${gtmContainerId}`;
rules.push({
id: rules.length + 1,
priority: 1,
action: {
type: "block"
},
condition: {
regexFilter: gtmContainerIdIsRegex ? gtmUrl : `https://www.googletagmanager.com/gtm.js\\?id=${gtmContainerId}`,
resourceTypes: ["script"]
}
});
console.log('Google Tag Manager blocked');
}
// Add Tracking Proxy blocking rule if blockTrackingProxy is enabled and scriptUrl matches
if (blockTrackingProxy && trackingProxyUrl && scriptUrlMatches(scriptUrl)) {
const proxyUrl = trackingProxyUrlIsRegex ? trackingProxyUrl : `^${trackingProxyUrl}$`;
rules.push({
id: rules.length + 2,
priority: 1,
action: {
type: "block"
},
condition: {
regexFilter: proxyUrl,
resourceTypes: ["script", "xmlhttprequest"]
}
});
console.log('Tracking Proxy blocked');
}
if (rules.length === 0) {
//console.log('No valid URLs to match, no rules set');
return;
}
// Update the rules
chrome.declarativeNetRequest.updateDynamicRules({
addRules: rules,
removeRuleIds: rules.map(rule => rule.id)
}, () => {
if (chrome.runtime.lastError) {
console.error('Error setting declarativeNetRequest rules:', chrome.runtime.lastError);
} else {
console.log('DeclarativeNetRequest rules set up successfully');
}
});
});
}
/*** Chrome Extension Function Library ***/
// Function to Initialise the Badge and add Request Header
chrome.runtime.onInstalled.addListener(() => {
// Clear badge on installation
clearBadge();
// Load options and set dynamic rules
loadOptionsAndSetRules();
});
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'sync') {
console.log('Options changed, updating rules');
loadOptionsAndSetRules();
}
});
// Listener for Extension Icon Clicks
chrome.action.onClicked.addListener((tab) => {
//console.log('Action button clicked', tab);
let tabId = tab.id;
let windowId = tab.windowId;
chrome.tabs.create({ url: 'popup.html' });
});
// Listener for internal messages
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
//console.log('TP Received message:', message);
if (message.type === 'CHECK_TPT') {
getCurrentTabId((tabId) => {
tpdm.activeTabId = tabId;
//console.log('CHECK_TPT (Tab: '+tabId+'):', message);
if (message.hasTPT) {
tpdm.data.eventCount = 0;
resetBadge(tabId);
if (typeof tpdm.data[tabId]!='object') tpdm.data[tabId] = JSON.parse(JSON.stringify(tpdm.tpl));
tpdm.data[tabId].tpId = message.tpobj.cid;
} else {
clearBadge(tabId);
}
});
} else if (message.type === 'TRACKING_PROXY_EVENT' && message.details) {
const eventData = message.details.eventData;
const metaData = message.details.metaData;
//console.log('TRACKING_PROXY_EVENT',message);
incrementBadge(metaData.tabId);
if (typeof tpdm.data[metaData.tabId]!='object') tpdm.data[metaData.tabId] = JSON.parse(JSON.stringify(tpdm.tpl));
tpdm.data[metaData.tabId].events.push(JSON.parse(JSON.stringify(eventData)));
} else if (message.type === 'REQUEST_POPUP_DATA') {
//getCurrentTabId((tabId) => {
if (typeof tpdm.data[tpdm.activeTabId]!='object') tpdm.data[tpdm.activeTabId] = JSON.parse(JSON.stringify(tpdm.tpl));
let obj = {
tpId: tpdm.data[tpdm.activeTabId].tpId,
tabId: tpdm.activeTabId,
events: tpdm.data[tpdm.activeTabId].events
};
//console.log('TP Received message:'+tpdm.data[tpdm.activeTabId].tpId, obj);
sendResponse(obj);
//});
} else {
//console.log('Unknown message type:', message.type);
}
});
// Listeners for Tab-Change
chrome.tabs.onActivated.addListener(function(tab) {
//console.log('TAB ACTIVATED', tab);
tpdm.activeTabId = tab.tabId;
tpdm.activeWindowId = tab.windowId;
});
// Listeners for Tab-Update
chrome.tabs.onUpdated.addListener(function(activeInfo, changeInfo, tab) {
let tabId = tab.id;
let windowId = tab.windowId;
if (typeof changeInfo == 'object' && typeof changeInfo.status == 'string') {
if (changeInfo.status == 'complete') {
//console.log('TAB Load complete: ' + tab.url, tab);
}
}
});
chrome.runtime.onStartup.addListener(function() {
// Initialisiert alle Tabs beim Start der Erweiterung
chrome.tabs.query({}, function(tabs) {
tabs.forEach(tab => {
resetBadge(tab.id);
});
});
});
chrome.webNavigation.onBeforeNavigate.addListener(function(details) {
if (details.frameId === 0) { // Haupt-Frame der Navigation
//console.log('Navigation started for Tab:', details.tabId);
resetBadge(details.tabId);
}
});
// Intercepting network requests to detect Tracking Proxy Events
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
if (details.method === "POST" && details.type === "xmlhttprequest" && details.tabId >= 0) {
let eventData = {};
if (details.requestBody) {
if (details.requestBody.formData) {
eventData = JSON.parse(JSON.stringify(details.requestBody.formData));
//console.log('TRACKING_PROXY_REQUEST 1', eventData);
} else if (details.requestBody.raw && details.requestBody.raw.length > 0 && details.requestBody.raw[0].bytes) {
//console.warn('TRACKING_PROXY_REQUEST 0', details);
try {
const decodedString = decodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(details.requestBody.raw[0].bytes)));
if (decodedString.charAt(0) === '{') {
eventData = JSON.parse(decodedString);
}
} catch (e) {
//console.error('Failed to parse request body as JSON:', e);
return;
}
} else {
//console.log('Payload is no JSON object:', details);
return;
}
}
if (!eventData || !eventData.cid) {
//console.log('Payload has no cid:', details);
return;
}
let metaData = { documentId:details.documentId, frameId:details.frameId, initiator:details.initiator, requestId:details.requestId, tabId:details.tabId, timeStamp:details.timeStamp, url:details.url };
//console.log('TRACKING_PROXY_REQUEST available', eventData);
chrome.tabs.query({ /*active: true, currentWindow: true*/ }, function(tabs) {
if (tabs.length > 0) {
//console.log('TRACKING_PROXY_REQUEST available', { eventData:eventData, metaData:metaData });
chrome.tabs.sendMessage(details.tabId, { type: 'TRACKING_PROXY_REQUEST', details:{eventData:eventData, metaData:metaData} }, (response) => {
if (chrome.runtime.lastError) {
//console.error('Error sending message to content script: '+chrome.runtime.lastError, { eventData:eventData, metaData:metaData });
}
//else { console.log('TRACKING_PROXY_REQUEST sent', { eventData:eventData, metaData:metaData }); }
});
}
});
}
},
{ urls: ["<all_urls>"] },
["requestBody"] // "blocking","requestBody","responseHeaders" | see: https://stackoverflow.com/questions/33106709/chrome-webrequest-doesnt-see-post-data-in-requestbody
);
// EOF