Reject ServiceWorker Auto (Simple)

Blocks ServiceWorker on all websites. Advanced whitelist management with manual domain removal. Prevents PWA installations and background sync.

// ==UserScript==
// @name        Reject ServiceWorker Auto (Simple)
// @namespace   rejectserviceWorkerAuto
// @version     1.7.5
// @description Blocks ServiceWorker on all websites. Advanced whitelist management with manual domain removal. Prevents PWA installations and background sync.
// @author      hongmd
// @license     MIT
// @homepageURL https://github.com/hongmd/userscript-improved
// @supportURL  https://github.com/hongmd/userscript-improved/issues
// @match       *://*/*
// @run-at      document-start
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @compatible  ScriptCat
// @compatible  Tampermonkey
// @compatible  Greasemonkey
// @noframes
// ==/UserScript==

'use strict';

// Constants for consistent messaging and configuration
const MESSAGES = {
    // Status messages
    BLOCKED: "ServiceWorker registration blocked",
    WHITELISTED: "Domain whitelisted - ServiceWorker allowed",
    ALREADY_EXISTS: "Domain already in whitelist",
    NOT_IN_WHITELIST: "Domain is not in whitelist",

    // Alert messages
    ADDED_TO_WHITELIST: (hostname) => `✅ Added "${hostname}" to whitelist!\n\nServiceWorker will NOT be blocked here.\nReload page to take effect.`,
    REMOVED_FROM_WHITELIST: (hostname) => `❌ Removed "${hostname}" from whitelist!\n\nServiceWorker will be blocked here.\nReload page to take effect.`,
    ALREADY_WHITELISTED: (hostname) => `Info: "${hostname}" is already in whitelist.`,
    NOT_WHITELISTED: (hostname) => `"${hostname}" is not in whitelist.`,
    WHITELIST_EMPTY: "📋 Whitelist is empty!\n\nNo domains to remove.",
    DOMAIN_NOT_FOUND: (domain, currentList) => `❌ Domain "${domain}" not found in whitelist.\n\nCurrent whitelist: ${currentList}`,
    MANUAL_REMOVAL_SUCCESS: (domain) => `✅ Removed "${domain}" from whitelist!\n\nServiceWorker will be blocked on this domain.\nReload the page if you're currently on it.`,

    // Error messages
    INIT_ERROR: "Failed to initialize menu items - falling back to blocking mode",
    STORAGE_ERROR: "Invalid stored data, resetting to empty array",
    INJECTION_ERROR: "Failed to block ServiceWorker - using fallback method",

    // Menu commands
    SHOW_WHITELIST: "📋 Show Whitelist Info",
    BLOCK_HERE: "🚫 Block ServiceWorker Here",
    ALLOW_HERE: "✅ Allow ServiceWorker Here",
    MANUAL_BLOCK: "🔧 Manual Block Now",
    REMOVE_FROM_WHITELIST: "🗑️ Remove from Whitelist"
};

const SCRIPT_NAME = 'rejectserviceWorkerAuto';
const STORAGE_PREFIX = `autoinject${SCRIPT_NAME}`;
const LOG_PREFIX = 'RSA:';

console.log(`${LOG_PREFIX} Script loaded for:`, document.domain);

let injectedStatus = false;
let hostArray = [];

/**
 * Injects ServiceWorker blocking logic using primary and fallback methods
 * Primary method: Overrides navigator.serviceWorker with a blocking proxy object
 * Fallback method: Overrides the register method directly for older browsers
 * Prevents PWA installations and background sync functionality
 */
function inject() {
    // Skip if running in frames or already injected
    if (window.self !== window.top) return;
    if (injectedStatus) return;

    // Block ServiceWorker registration
    if (typeof navigator !== 'undefined' && navigator.serviceWorker) {
        try {
            Object.defineProperty(navigator, 'serviceWorker', {
                value: {
                    register: () => Promise.reject(new Error("ServiceWorker registration blocked by RSA script")),
                    getRegistration: () => Promise.resolve(undefined),
                    getRegistrations: () => Promise.resolve([]),
                    ready: Promise.reject(new Error("ServiceWorker blocked"))
                },
                writable: false,
                configurable: false
            });
            console.log(`${LOG_PREFIX} ${MESSAGES.BLOCKED} on`, document.domain);
        } catch (e) {
            // Fallback method for older browsers
            navigator.serviceWorker.register = () => Promise.reject(new Error("ServiceWorker registration blocked"));
            console.warn(`${LOG_PREFIX} ${MESSAGES.INJECTION_ERROR} on`, document.domain);
        }
    }
    injectedStatus = true;
}

/**
 * Adds the current domain to the whitelist, allowing ServiceWorker registration
 * Updates persistent storage and provides user feedback
 * Requires page reload to take effect
 */
function addHost() {
    const hostname = location.hostname;
    if (!hostArray.includes(hostname)) {
        hostArray.push(hostname);
        GM_setValue(STORAGE_PREFIX, JSON.stringify(hostArray));
        console.log(`${LOG_PREFIX} Added`, hostname, 'to whitelist');
        alert(MESSAGES.ADDED_TO_WHITELIST(hostname));
    } else {
        alert(MESSAGES.ALREADY_WHITELISTED(hostname));
    }
}

/**
 * Displays comprehensive whitelist information including current domain status
 * Shows total count and lists all whitelisted domains
 * Provides clear visual indicators for whitelist status
 */
function showWhitelistInfo() {
    const hostname = location.hostname;
    const isWhitelisted = hostArray.includes(hostname);
    const currentStatus = isWhitelisted ? "WHITELISTED" : "BLOCKED";
    const statusIcon = isWhitelisted ? "✅" : "🚫";
    const totalSites = hostArray.length;

    let message = `${statusIcon} Current domain: ${hostname}\n`;
    message += `Status: ${currentStatus}\n\n`;
    message += `📋 Total whitelisted sites: ${totalSites}\n`;

    if (totalSites > 0) {
        message += "🔸 " + hostArray.join("\n🔸 ");
    }

    alert(message);
}

/**
 * Removes the current domain from the whitelist, enabling ServiceWorker blocking
 * Updates persistent storage and provides user feedback
 * Requires page reload to take effect
 */
function removeHost() {
    const hostname = location.hostname;
    const index = hostArray.indexOf(hostname);
    if (index > -1) {
        hostArray.splice(index, 1);
        GM_setValue(STORAGE_PREFIX, JSON.stringify(hostArray));
        console.log(`${LOG_PREFIX} Removed`, hostname, 'from whitelist');
        alert(MESSAGES.REMOVED_FROM_WHITELIST(hostname));
    } else {
        alert(MESSAGES.NOT_WHITELISTED(hostname));
    }
}

/**
 * Allows user to remove any domain from the whitelist by entering it manually
 * Provides a text input dialog for domain removal
 * Useful for managing whitelist without visiting each domain
 */
function removeFromWhitelist() {
    if (hostArray.length === 0) {
        alert(MESSAGES.WHITELIST_EMPTY);
        return;
    }

    const currentList = hostArray.join(", ");
    const domainToRemove = prompt(
        `🗑️ Remove domain from whitelist:\n\nCurrent whitelist: ${currentList}\n\nEnter domain to remove (without https://):`,
        ""
    );

    if (!domainToRemove?.trim()) return;

    const cleanDomain = domainToRemove.trim().toLowerCase();
    const index = hostArray.findIndex(host => host.toLowerCase() === cleanDomain);

    if (index > -1) {
        const removedDomain = hostArray.splice(index, 1)[0];
        GM_setValue(STORAGE_PREFIX, JSON.stringify(hostArray));
        console.log(`${LOG_PREFIX} Manually removed`, removedDomain, 'from whitelist');
        alert(MESSAGES.MANUAL_REMOVAL_SUCCESS(removedDomain));
    } else {
        alert(MESSAGES.DOMAIN_NOT_FOUND(cleanDomain, currentList));
    }
}

/**
 * Initializes the script by loading stored whitelist data and setting up menu commands
 * Handles data validation, error recovery, and dynamic menu configuration
 * Automatically injects blocking logic for non-whitelisted domains
 */
function initializeScript() {
    try {
        // Safe JSON parsing with validation
        const storedData = GM_getValue(STORAGE_PREFIX, "[]");
        if (typeof storedData === 'string' && storedData.trim()) {
            hostArray = JSON.parse(storedData);
            // Validate that result is an array
            if (!Array.isArray(hostArray)) {
                console.warn(`${LOG_PREFIX} ${MESSAGES.STORAGE_ERROR}`);
                hostArray = [];
            }
        } else {
            hostArray = [];
        }

        const hostname = location.hostname;
        const isWhitelisted = hostArray.includes(hostname);

        // Always show status info
        GM_registerMenuCommand(MESSAGES.SHOW_WHITELIST, showWhitelistInfo);
        GM_registerMenuCommand(MESSAGES.REMOVE_FROM_WHITELIST, removeFromWhitelist);

        if (isWhitelisted) {
            // Current domain is whitelisted
            GM_registerMenuCommand(MESSAGES.BLOCK_HERE, removeHost);
            GM_registerMenuCommand(MESSAGES.MANUAL_BLOCK, inject);
            console.log(`${LOG_PREFIX}`, hostname, MESSAGES.WHITELISTED);
        } else {
            // Current domain is not whitelisted (blocked by default)
            inject();
            GM_registerMenuCommand(MESSAGES.ALLOW_HERE, addHost);
            console.log(`${LOG_PREFIX} Auto-blocked ServiceWorker for`, hostname);
        }
    } catch (err) {
        console.error(`${LOG_PREFIX} ${MESSAGES.INIT_ERROR}`);
        console.error(err);
        // Fallback: always inject if there's an error
        inject();
    }
}

// Start the script
initializeScript();