Greasy Fork is available in English.
Production-grade privacy shield. Split-phase init, dynamic reconfig, extensible rules.
// ==UserScript==
// @name Safety Shield Pro v1
// @namespace https://greasyfork.org/fr/scripts/565749-safety-shield-pro/code
// @version 1.0.1
// @license MIT
// @description Production-grade privacy shield. Split-phase init, dynamic reconfig, extensible rules.
// @match *://*/*
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function () {
'use strict';
const VERSION = '6.0';
const host = location.host;
const EventBus = {
listeners: {},
on(event, callback) {
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
},
emit(event, data) {
if (!this.listeners[event]) return;
this.listeners[event].forEach(cb => {
try {
cb(data);
} catch (err) {
console.error(`[EventBus] Error in ${event}:`, err);
}
});
},
off(event, callback) {
if (!this.listeners[event]) return;
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
}
};
const SafetyShield = {
version: VERSION,
host: host,
profile: 'balanced',
initialized: {
core: false,
ui: false
},
state: {
blockedLog: [],
stats: {}
},
settings: {},
modules: {},
ui: {},
observers: [],
rules: {
trackers: [],
ads: []
},
debug: {
enabled: false,
log: []
}
};
const BUILTIN_RULES = {
trackers: [
{ domain: 'google-analytics.com' },
{ domain: 'analytics.google.com' },
{ domain: 'facebook.com' },
{ domain: 'fbcdn.net' },
{ domain: 'doubleclick.net' },
{ domain: 'googlesyndication.com' },
{ domain: 'amazon-adsystem.com' },
{ domain: 'hotjar.com' },
{ domain: 'mixpanel.com' },
{ domain: 'segment.com' },
{ domain: 'amplitude.com' },
{ domain: 'intercom.io' },
{ domain: 'drift.com' },
{ domain: 'optimizely.com' }
],
ads: [
{ domain: 'doubleclick.net' },
{ domain: 'googlesyndication.com' },
{ domain: 'amazon-adsystem.com' },
{ regex: /adserver|adnetwork|adexchange|ads\./ }
]
};
const Debug = {
enable() {
SafetyShield.debug.enabled = true;
console.log('[SAFETY SHIELD] Debug mode enabled');
},
disable() {
SafetyShield.debug.enabled = false;
console.log('[SAFETY SHIELD] Debug mode disabled');
},
log(msg) {
if (!SafetyShield.debug.enabled) return;
SafetyShield.debug.log.push({
msg,
time: new Date().toLocaleTimeString()
});
console.log(`[DEBUG] ${msg}`);
},
inspect() {
return {
profile: SafetyShield.profile,
settings: SafetyShield.settings,
observersActive: SafetyShield.observers.length,
trackerRules: SafetyShield.rules.trackers.length,
adRules: SafetyShield.rules.ads.length,
stats: SafetyShield.state.stats,
recentBlocks: SafetyShield.state.blockedLog.slice(-20)
};
}
};
const PROFILES = {
relaxed: {
exeBlockEnabled: true,
trackerBlockEnabled: false,
adBlockEnabled: false,
redirectBlockEnabled: false,
dnsPrefetchBlock: false,
fingerprintProtect: 'off',
scriptInjectionBlock: 'off'
},
balanced: {
exeBlockEnabled: true,
trackerBlockEnabled: true,
adBlockEnabled: true,
redirectBlockEnabled: false,
dnsPrefetchBlock: true,
fingerprintProtect: 'light',
scriptInjectionBlock: 'external'
},
paranoid: {
exeBlockEnabled: true,
trackerBlockEnabled: true,
adBlockEnabled: true,
redirectBlockEnabled: true,
dnsPrefetchBlock: true,
fingerprintProtect: 'aggressive',
scriptInjectionBlock: 'external'
}
};
const SettingsManager = {
defaults: PROFILES.balanced,
init() {
const savedProfile = GM_getValue('profile');
if (savedProfile && PROFILES[savedProfile]) {
SafetyShield.profile = savedProfile;
const profile = PROFILES[savedProfile];
for (const [key, value] of Object.entries(profile)) {
SafetyShield.settings[key] = value;
}
} else {
for (const [key, value] of Object.entries(this.defaults)) {
const stored = GM_getValue(key);
SafetyShield.settings[key] = stored !== undefined ? stored : value;
}
}
Debug.log('Settings initialized');
},
get(key) {
return SafetyShield.settings[key] ?? this.defaults[key];
},
set(key, value) {
const oldValue = SafetyShield.settings[key];
SafetyShield.settings[key] = value;
GM_setValue(key, value);
GM_setValue('profile', null);
Debug.log(`Setting ${key}: ${oldValue} → ${value}`);
EventBus.emit('setting-changed', { key, oldValue, newValue: value });
},
setProfile(profileName) {
if (!PROFILES[profileName]) return false;
SafetyShield.profile = profileName;
GM_setValue('profile', profileName);
const profile = PROFILES[profileName];
for (const [key, value] of Object.entries(profile)) {
SafetyShield.settings[key] = value;
GM_setValue(key, value);
}
Debug.log(`Profile switched to: ${profileName}`);
EventBus.emit('profile-changed', { profile: profileName });
return true;
}
};
const RulesManager = {
init() {
SafetyShield.rules.trackers = [...BUILTIN_RULES.trackers];
SafetyShield.rules.ads = [...BUILTIN_RULES.ads];
},
addTrackerRule(rule) {
SafetyShield.rules.trackers.push(rule);
Debug.log(`Added tracker rule: ${rule.domain || rule.regex}`);
},
addAdRule(rule) {
SafetyShield.rules.ads.push(rule);
Debug.log(`Added ad rule: ${rule.domain || rule.regex}`);
},
matchesTracker(url) {
return this._matchRules(url, SafetyShield.rules.trackers);
},
matchesAd(url) {
return this._matchRules(url, SafetyShield.rules.ads);
},
_matchRules(url, rules) {
try {
const hostname = new URL(url, location.href).hostname;
return rules.some(rule => {
if (rule.domain) {
return hostname === rule.domain || hostname.endsWith('.' + rule.domain);
}
if (rule.regex && rule.regex instanceof RegExp) {
return rule.regex.test(hostname);
}
return false;
});
} catch {
return false;
}
}
};
const Logger = {
MAX_ENTRIES: 150,
log(url, type) {
const entry = {
url: String(url || '').substring(0, 120),
type,
time: new Date().toLocaleTimeString()
};
SafetyShield.state.blockedLog.push(entry);
if (SafetyShield.state.blockedLog.length > this.MAX_ENTRIES) {
SafetyShield.state.blockedLog.shift();
}
SafetyShield.state.stats[type] = (SafetyShield.state.stats[type] || 0) + 1;
Debug.log(`Blocked ${type}: ${url}`);
EventBus.emit('block', { url, type, total: SafetyShield.state.blockedLog.length });
},
clear() {
SafetyShield.state.blockedLog = [];
SafetyShield.state.stats = {};
Debug.log('Log cleared');
EventBus.emit('log-cleared', {});
},
getTotal() {
return SafetyShield.state.blockedLog.length;
}
};
const Detectors = {
getHostname(url) {
try {
if (!url || typeof url !== 'string') return '';
return new URL(url, location.href).hostname;
} catch {
return '';
}
},
isExternal(url) {
try {
if (!url || typeof url !== 'string') return false;
const urlObj = new URL(url, location.href);
return urlObj.host !== host;
} catch {
return false;
}
},
isExe(url) {
return SettingsManager.get('exeBlockEnabled') &&
typeof url === 'string' &&
/\.exe(\?|#|$)/i.test(url);
},
isTracker(url) {
if (!SettingsManager.get('trackerBlockEnabled') || !url || typeof url !== 'string') {
return false;
}
return RulesManager.matchesTracker(url);
},
isAd(url) {
if (!SettingsManager.get('adBlockEnabled') || !url || typeof url !== 'string') {
return false;
}
return RulesManager.matchesAd(url);
}
};
SafetyShield.modules.redirect = {
name: 'redirect',
enabled: false,
originalAssign: location.assign,
originalReplace: location.replace,
originalOpen: window.open,
originalHrefDescriptor: null,
shouldBlock(url) {
if (!url || typeof url !== 'string') return false;
if (Detectors.isExe(url)) {
Logger.log(url, 'exe');
return true;
}
if (Detectors.isTracker(url)) {
Logger.log(url, 'tracker');
return true;
}
if (Detectors.isAd(url)) {
Logger.log(url, 'ad');
return true;
}
if (SettingsManager.get('redirectBlockEnabled') && Detectors.isExternal(url)) {
Logger.log(url, 'redirect');
return true;
}
return false;
},
initCore() {
if (this.enabled) return;
const self = this;
const guard = (fn, originalFn) => {
const wrapper = function (url, ...args) {
if (self.shouldBlock(url)) return undefined;
return fn.call(this, url, ...args);
};
wrapper.toString = () => originalFn.toString();
return wrapper;
};
location.assign = guard(this.originalAssign, this.originalAssign);
location.replace = guard(this.originalReplace, this.originalReplace);
window.open = guard(this.originalOpen, this.originalOpen);
this.patchLocationHref();
this.enabled = true;
Debug.log('Redirect module: Core phase initialized');
},
initUI() {
if (!this.enabled) {
this.initCore();
}
this.setupLinkProtection();
Debug.log('Redirect module: UI phase initialized');
},
patchLocationHref() {
const self = this;
try {
const locationProto = Location.prototype;
const hrefDescriptor = Object.getOwnPropertyDescriptor(locationProto, 'href');
if (!hrefDescriptor) {
Debug.log('Could not get href descriptor (unsupported browser)');
return;
}
this.originalHrefDescriptor = hrefDescriptor;
Object.defineProperty(locationProto, 'href', {
get: hrefDescriptor.get,
set(url) {
if (self.shouldBlock(url)) {
return;
}
hrefDescriptor.set.call(this, url);
},
configurable: true
});
Debug.log('location.href patched');
} catch (err) {
Debug.log(`Could not patch location.href: ${err.message}`);
}
},
setupLinkProtection() {
const self = this;
document.addEventListener("click", (e) => {
const a = e.target.closest("a[href]");
if (!a || !a.href || typeof a.href !== 'string') return;
if (self.shouldBlock(a.href)) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
}, true);
},
enable() {
if (this.enabled) return;
this.initCore();
this.initUI();
},
disable() {
if (!this.enabled) return;
location.assign = this.originalAssign;
location.replace = this.originalReplace;
window.open = this.originalOpen;
if (this.originalHrefDescriptor) {
Object.defineProperty(Location.prototype, 'href', this.originalHrefDescriptor);
}
this.enabled = false;
Debug.log('Redirect module disabled');
}
};
SafetyShield.modules.tracker = {
name: 'tracker',
enabled: false,
observer: null,
timeout: null,
initCore() {
if (!SettingsManager.get('trackerBlockEnabled')) return;
this.removeDnsPrefetch();
this.cleanTrackers();
},
initUI() {
if (!SettingsManager.get('trackerBlockEnabled')) return;
if (!this.enabled) {
this.startObserver();
this.enabled = true;
Debug.log('Tracker module initialized');
}
},
removeDnsPrefetch() {
if (!SettingsManager.get('dnsPrefetchBlock')) return;
try {
document.querySelectorAll('link[rel="dns-prefetch"]').forEach(link => {
Logger.log(link.href, 'dns-prefetch');
link.remove();
});
} catch (err) {}
},
cleanTrackers() {
const selectors = [
'img[src*="analytics"]',
'img[src*="track"]',
'img[src*="pixel"]',
'img[src*="beacon"]'
];
selectors.forEach(sel => {
try {
document.querySelectorAll(sel).forEach(el => {
if (el.src && Detectors.isTracker(el.src)) {
Logger.log(el.src, 'tracking-pixel');
el.remove();
}
});
} catch (err) {}
});
},
startObserver() {
if (this.observer) return;
const self = this;
this.observer = new MutationObserver(() => {
clearTimeout(self.timeout);
self.timeout = setTimeout(() => self.cleanTrackers(), 100);
});
try {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true
});
SafetyShield.observers.push(this.observer);
} catch (err) {}
},
enable() {
if (this.enabled) return;
SettingsManager.set('trackerBlockEnabled', true);
this.initUI();
},
disable() {
if (!this.enabled) return;
if (this.observer) {
this.observer.disconnect();
SafetyShield.observers = SafetyShield.observers.filter(o => o !== this.observer);
this.observer = null;
}
clearTimeout(this.timeout);
this.enabled = false;
Debug.log('Tracker module disabled');
}
};
SafetyShield.modules.ads = {
name: 'ads',
enabled: false,
observer: null,
timeout: null,
initCore() {
if (!SettingsManager.get('adBlockEnabled')) return;
this.cleanAds();
},
initUI() {
if (!SettingsManager.get('adBlockEnabled')) return;
if (!this.enabled) {
this.startObserver();
this.enabled = true;
Debug.log('Ad blocking module initialized');
}
},
cleanAds() {
const adSelectors = [
'[id*="ad-"]',
'[id*="-ad"]',
'[class*=" ad-"]',
'[class*="-ad-"]',
'[class*="advert"]',
'[data-ad-slot]',
'[data-ad-format]'
];
adSelectors.forEach(sel => {
try {
document.querySelectorAll(sel).forEach(el => {
try {
const identifier = el.src || el.id || el.className || '';
if (Detectors.isAd(identifier)) {
Logger.log(identifier, 'ad');
el.remove();
}
} catch (e) {}
});
} catch (err) {}
});
try {
document.querySelectorAll("iframe").forEach(f => {
try {
if (f.src && (Detectors.isTracker(f.src) || Detectors.isAd(f.src))) {
Logger.log(f.src, 'external-iframe');
f.remove();
}
} catch (e) {}
});
} catch (err) {}
try {
document.querySelectorAll('meta[http-equiv="refresh"]').forEach(m => {
Logger.log(m.content, 'meta-refresh');
m.remove();
});
} catch (err) {}
},
startObserver() {
if (this.observer) return;
const self = this;
this.observer = new MutationObserver(() => {
clearTimeout(self.timeout);
self.timeout = setTimeout(() => self.cleanAds(), 150);
});
try {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true
});
SafetyShield.observers.push(this.observer);
} catch (err) {}
},
enable() {
if (this.enabled) return;
SettingsManager.set('adBlockEnabled', true);
this.initUI();
},
disable() {
if (!this.enabled) return;
if (this.observer) {
this.observer.disconnect();
SafetyShield.observers = SafetyShield.observers.filter(o => o !== this.observer);
this.observer = null;
}
clearTimeout(this.timeout);
this.enabled = false;
Debug.log('Ad blocking module disabled');
}
};
SafetyShield.modules.scriptInjection = {
name: 'scriptInjection',
enabled: false,
observer: null,
initCore() {
},
initUI() {
const mode = SettingsManager.get('scriptInjectionBlock');
if (mode === 'off' || this.enabled) return;
this.startObserver();
this.enabled = true;
Debug.log('Script injection module initialized');
},
blockScript(node) {
const src = node.src || '';
if (src && (Detectors.isTracker(src) || Detectors.isAd(src))) {
Logger.log(src, 'external-script');
return true;
}
return false;
},
startObserver() {
if (this.observer) return;
const self = this;
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
try {
if (node.tagName === 'SCRIPT' && self.blockScript(node)) {
node.remove();
}
} catch (e) {}
});
});
});
try {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true
});
SafetyShield.observers.push(this.observer);
} catch (err) {}
},
enable() {
if (this.enabled) return;
SettingsManager.set('scriptInjectionBlock', 'external');
this.initUI();
},
disable() {
if (!this.enabled) return;
if (this.observer) {
this.observer.disconnect();
SafetyShield.observers = SafetyShield.observers.filter(o => o !== this.observer);
this.observer = null;
}
this.enabled = false;
Debug.log('Script injection module disabled');
}
};
SafetyShield.modules.fingerprint = {
name: 'fingerprint',
enabled: false,
originalCanvasToDataURL: null,
originalWebGLGetParameter: null,
initCore() {
const mode = SettingsManager.get('fingerprintProtect');
if (mode === 'off') return;
this.spoof();
if (mode === 'light') {
this.protectCanvas();
} else if (mode === 'aggressive') {
this.protectCanvas();
this.protectWebGL();
}
this.enabled = true;
Debug.log('Fingerprint protection initialized');
},
initUI() {
},
spoof() {
try {
Object.defineProperty(navigator, 'language', {
get: () => 'en-US',
configurable: true
});
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
configurable: true
});
Object.defineProperty(navigator, 'platform', {
get: () => 'Linux x86_64',
configurable: true
});
} catch (err) {}
},
protectCanvas() {
try {
this.originalCanvasToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalFn = this.originalCanvasToDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
Logger.log('canvas.toDataURL', 'fingerprint-attempt');
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
};
HTMLCanvasElement.prototype.toDataURL.toString = () => originalFn.toString();
} catch (err) {}
},
protectWebGL() {
try {
this.originalWebGLGetParameter = WebGLRenderingContext.prototype.getParameter;
const originalFn = this.originalWebGLGetParameter;
WebGLRenderingContext.prototype.getParameter = function (parameter) {
if (parameter === 37445 || parameter === 37446) {
Logger.log(`WebGL.parameter(${parameter})`, 'fingerprint-attempt');
return 'Generic GPU';
}
return originalFn.call(this, parameter);
};
WebGLRenderingContext.prototype.getParameter.toString = () => originalFn.toString();
} catch (err) {}
try {
const originalFn2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function (parameter) {
if (parameter === 37445 || parameter === 37446) {
return 'Generic GPU';
}
return originalFn2.call(this, parameter);
};
} catch (err) {}
},
enable() {
if (this.enabled) return;
SettingsManager.set('fingerprintProtect', 'light');
this.initCore();
},
disable() {
if (!this.enabled) return;
if (this.originalCanvasToDataURL) {
HTMLCanvasElement.prototype.toDataURL = this.originalCanvasToDataURL;
}
if (this.originalWebGLGetParameter) {
WebGLRenderingContext.prototype.getParameter = this.originalWebGLGetParameter;
}
this.enabled = false;
Debug.log('Fingerprint protection disabled');
}
};
SafetyShield.ui.statusButton = null;
SafetyShield.ui.createButton = () => {
const btn = document.createElement("div");
btn.id = "safety-shield-status";
btn.style.cssText = `
position: fixed;
bottom: 12px;
right: 12px;
z-index: 999999;
padding: 6px 10px;
font-size: 11px;
font-family: monospace;
cursor: pointer;
border-radius: 4px;
background: rgba(15, 23, 42, 0.9);
color: #22c55e;
opacity: 0.7;
border: 1px solid #16a34a;
transition: all 0.2s;
user-select: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
`;
btn.onmouseover = () => {
btn.style.opacity = "1";
btn.style.boxShadow = "0 4px 12px rgba(34, 197, 94, 0.4)";
};
btn.onmouseout = () => {
btn.style.opacity = "0.7";
btn.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.3)";
};
document.documentElement.appendChild(btn);
return btn;
};
SafetyShield.ui.updateButton = () => {
if (!SafetyShield.ui.statusButton) return;
const total = Logger.getTotal();
const status = [
SettingsManager.get('exeBlockEnabled') ? 'E' : '',
SettingsManager.get('trackerBlockEnabled') ? 'T' : '',
SettingsManager.get('adBlockEnabled') ? 'A' : '',
SettingsManager.get('redirectBlockEnabled') ? 'R' : ''
].filter(Boolean).join('');
const display = status ? `${status} (${total})` : `○ (${total})`;
SafetyShield.ui.statusButton.textContent = display;
SafetyShield.ui.statusButton.title =
`Safety Shield v${VERSION}\nProfile: ${SafetyShield.profile}\nBlocked: ${total}\nPress ? for help | D for debug`;
};
SafetyShield.ui.showHelp = () => {
const existing = document.getElementById('safety-shield-help');
if (existing) {
existing.remove();
return;
}
const helpDiv = document.createElement('div');
helpDiv.id = 'safety-shield-help';
helpDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99999999;
background: rgba(15, 23, 42, 0.98);
color: #22c55e;
padding: 24px;
border-radius: 8px;
border: 2px solid #16a34a;
font-family: 'Courier New', monospace;
font-size: 12px;
max-width: 540px;
max-height: 720px;
overflow-y: auto;
line-height: 1.7;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
`;
helpDiv.innerHTML = `
<div style="margin-bottom: 16px;">
<strong style="color: #60a5fa; font-size: 14px;">Safety Shield v${VERSION}</strong>
<div style="font-size: 10px; color: #888; margin-top: 4px;">Profile: <strong>${SafetyShield.profile}</strong></div>
</div>
<div style="margin-bottom: 12px; border-bottom: 1px solid #16a34a; padding-bottom: 8px;">
<strong style="color: #60a5fa;">⌨️ KEYBOARD SHORTCUTS:</strong>
</div>
<div style="margin-bottom: 16px; font-size: 11px;">
<strong style="color: #4ade80;">L</strong> - Toggle EXE blocking<br>
<strong style="color: #4ade80;">T</strong> - Toggle tracker blocking<br>
<strong style="color: #4ade80;">A</strong> - Toggle ad blocking<br>
<strong style="color: #4ade80;">R</strong> - Toggle redirect blocking<br>
<strong style="color: #4ade80;">C</strong> - Clear blocked log<br>
<strong style="color: #4ade80;">1/2/3</strong> - Profiles (relaxed/balanced/paranoid)<br>
<strong style="color: #4ade80;">D</strong> - Toggle debug mode (F12)<br>
<strong style="color: #4ade80;">?</strong> - Toggle this help<br>
</div>
<div style="margin-bottom: 12px; border-bottom: 1px solid #16a34a; padding-bottom: 8px;">
<strong style="color: #60a5fa;">PROFILES:</strong>
</div>
<div style="font-size: 10px; margin-bottom: 16px; background: rgba(34, 197, 94, 0.1); padding: 8px; border-radius: 4px;">
<strong>1 - Relaxed:</strong> EXE only<br>
<strong>2 - Balanced:</strong> Standard (recommended)<br>
<strong>3 - Paranoid:</strong> Maximum protection<br>
</div>
<div style="font-size: 10px; color: #888;">
<strong>⚠️ HONEST ASSESSMENT:</strong><br>
This script provides <strong>low false positives</strong>, not zero. Some sites require adjustments:
<ul style="margin: 4px 0; padding-left: 16px;">
<li>OAuth logins (Google, GitHub)</li>
<li>Payment processors (Stripe, PayPal)</li>
<li>CAPTCHA services</li>
</ul>
Use profile <strong>1</strong> or <strong>2</strong> if issues occur.
</div>
<div style="margin-top: 12px; background: rgba(34, 197, 94, 0.1); padding: 8px; border-radius: 4px; font-size: 10px;">
<strong>TECHNICAL:</strong><br>
Race condition fixed: Split-phase init (core at document-start, UI at DOMContentLoaded).<br>
Extensible rules system. Dynamic module enable/disable. Debug mode available.
</div>
<div style="text-align: center; cursor: pointer; padding: 10px; background: rgba(34, 197, 94, 0.2); border-radius: 4px; user-select: none; border: 1px solid rgba(34, 197, 94, 0.4); margin-top: 12px;">
<strong>Press ? to close</strong>
</div>
`;
document.documentElement.appendChild(helpDiv);
helpDiv.addEventListener('click', SafetyShield.ui.showHelp);
};
EventBus.on('block', () => {
SafetyShield.ui.updateButton();
});
EventBus.on('log-cleared', () => {
SafetyShield.ui.updateButton();
});
EventBus.on('setting-changed', (data) => {
SafetyShield.ui.updateButton();
});
EventBus.on('profile-changed', () => {
SafetyShield.ui.updateButton();
});
document.addEventListener("keydown", (e) => {
if (e.target.tagName.match(/INPUT|TEXTAREA|SELECT/)) return;
const key = e.key.toLowerCase();
const hasModifier = e.ctrlKey || e.metaKey || e.altKey;
if (!hasModifier) {
if (key === 'l') {
SettingsManager.set('exeBlockEnabled', !SettingsManager.get('exeBlockEnabled'));
}
if (key === 't') {
if (SettingsManager.get('trackerBlockEnabled')) {
SafetyShield.modules.tracker.disable();
} else {
SafetyShield.modules.tracker.enable();
}
}
if (key === 'a') {
if (SettingsManager.get('adBlockEnabled')) {
SafetyShield.modules.ads.disable();
} else {
SafetyShield.modules.ads.enable();
}
}
if (key === 'r') {
SettingsManager.set('redirectBlockEnabled', !SettingsManager.get('redirectBlockEnabled'));
}
if (key === 'c') {
Logger.clear();
}
if (key === 'd') {
SafetyShield.debug.enabled ? Debug.disable() : Debug.enable();
}
if (key === '1') {
SettingsManager.setProfile('relaxed');
}
if (key === '2') {
SettingsManager.setProfile('balanced');
}
if (key === '3') {
SettingsManager.setProfile('paranoid');
}
}
if (key === '?') {
e.preventDefault();
SafetyShield.ui.showHelp();
}
});
window.addEventListener('beforeunload', () => {
SafetyShield.observers.forEach(obs => {
try {
obs.disconnect();
} catch (err) {}
});
SafetyShield.modules.tracker.disable();
SafetyShield.modules.ads.disable();
SafetyShield.modules.scriptInjection.disable();
});
const initCore = () => {
SettingsManager.init();
RulesManager.init();
SafetyShield.modules.redirect.initCore();
SafetyShield.modules.tracker.initCore();
SafetyShield.modules.ads.initCore();
SafetyShield.modules.fingerprint.initCore();
SafetyShield.initialized.core = true;
Debug.log('Core initialization complete (document-start)');
};
const initUI = () => {
SafetyShield.modules.redirect.initUI();
SafetyShield.modules.tracker.initUI();
SafetyShield.modules.ads.initUI();
SafetyShield.modules.scriptInjection.initUI();
SafetyShield.ui.statusButton = SafetyShield.ui.createButton();
SafetyShield.ui.updateButton();
SafetyShield.initialized.ui = true;
console.log(
`%c[SAFETY SHIELD] v${VERSION}\n` +
`Split-phase init • Extensible rules • Dynamic reconfiguration\n` +
`Press ? for help | D for debug | 1/2/3 for profiles`,
'color: #22c55e; font-weight: bold; font-size: 13px'
);
};
initCore();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUI);
} else {
initUI();
}
window.SafetyShield = {
version: VERSION,
debug: Debug,
rules: RulesManager,
modules: SafetyShield.modules,
settings: SettingsManager,
getState: () => Debug.inspect()
};
})();