tracking-proxy-helper/worker.js
Andi Petzoldt 1c1479b239 Tracking Proxy Helper v1.1
- added Session ID to Popup
- added Channels to Popup
2024-07-31 16:49:16 +02:00

331 lines
12 KiB
JavaScript

/***** Service Worker JS for Chrome Extension: Tracking Proxy Helper *****/
/*** Initialisation ***/
let tpdm = { activeTabId: null, activeWindowId: null, extInit: false, tpl: { tpId:'', sessId:'', channels:[], 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.chaChanged) {
tpdm.data[tabId].channels = message.tpobj.cha;
} else if (message.hasSessID) {
tpdm.data[tabId].sessId = message.tpobj.fip;
} else 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;
tpdm.data[tabId].sessId = message.tpobj.fip;
tpdm.data[tabId].channels = message.tpobj.cha;
} 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,
sessId: tpdm.data[tpdm.activeTabId].sessId,
tabId: tpdm.activeTabId,
channels: tpdm.data[tpdm.activeTabId].channels,
events: tpdm.data[tpdm.activeTabId].events
};
//console.log('TP Received message:'+tpdm.data[tpdm.activeTabId].sessId, JSON.parse(JSON.stringify(tpdm.data[tpdm.activeTabId])));
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.fip) {
//console.log('Payload has no fip:', 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