323 lines
11 KiB
JavaScript
323 lines
11 KiB
JavaScript
/***** 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
|