core/killswitch.js

/**
 * @module core/killswitch
 * @description Global killswitch system for WikiShield.
 *
 * This module provides a simple polling mechanism to check a remote Wikipedia page
 * for killswitch commands that can either disable WikiShield entirely or force a reload.
 *
 * @example
 * import { killswitch_status, startKillswitchPolling } from './core/killswitch.js';
 *
 * // Check if WikiShield is disabled
 * if (killswitch_status.disabled) {
 *   console.log("WikiShield is disabled by killswitch");
 *   return;
 * }
 *
 * // Start monitoring the killswitch
 * startKillswitchPolling(api);
 */

/**
 * Configuration for the killswitch polling system.
 *
 * @type {Object}
 * @property {string} killswitch_page - Wikipedia page containing killswitch configuration JSON
 * @property {number} polling_interval - How often to check the killswitch page (milliseconds)
 *
 * @example
 * // To set up the killswitch, create a page at the configured location with this content:
 * // Page: User:LuniZunie/killswitch.js
 * // Content:
 * {
 *   "disabled": false,
 *   "forceReload": false
 * }
 *
 * // To disable WikiShield:
 * {
 *   "disabled": true,
 *   "forceReload": false
 * }
 *
 * // To force all users to reload:
 * {
 *   "disabled": false,
 *   "forceReload": true
 * }
 */
export const killswitch_config = {
    killswitch_page: "User:LuniZunie/killswitch.js",
    polling_interval: 10000 // Check every 10 seconds
};

/**
 * @typedef {ReloadObject}
 * @property {number} soft - soft reload version
 * @property {number} hard - hard reload version
 */

/**
 * Killswitch status object with two simple options.
 *
 * @type {Object}
 * @property {boolean} disabled - If true, WikiShield should not load or operate
 * @property {ReloadObject} reload - Reload options
 *
 * @example
 * // Check if WikiShield is disabled
 * if (killswitch_status.disabled) {
 *   console.log("WikiShield is disabled");
 *   return;
 * }
 */

export const killswitch_status = {
    disabled: false,
    reload: {
        soft: false,
        hard: false,
    },

    alerts: [ ]
};

/**
 * Check the remote killswitch page and update the global killswitch status.
 *
 * This function fetches the killswitch configuration page from Wikipedia,
 * parses the JSON content, and updates the killswitch_status object.
 * If forceReload is detected, it will reload the page immediately.
 *
 * @async
 * @param {WikiShieldAPI} api - The WikiShield API instance for fetching page content
 * @returns {Promise<Object>} The updated killswitch_status object
 *
 * @example
 * const status = await checkKillswitch(wikishield.api);
 * if (status.disabled) {
 *   console.log("WikiShield has been disabled!");
 * }
 */
export async function checkKillswitch(api, startup = true) {
    try {
        const domain = mw.config.get("wgServerName") === "test.wikipedia.org" ? "test.wikipedia.org" : "en.wikipedia.org";

        const content = await fetch(`https://${mw.config.get("wgServer")}/w/api.php?action=query&prop=revisions&rvprop=content&format=json&origin=*&titles=${encodeURIComponent(killswitch_config.killswitch_page)}`)
            .then(response => response.json())
            .then(data => {
                const pages = data.query.pages;
                const pageId = Object.keys(pages)[0];
                return pages[pageId].revisions[0]['*'];
            });

        // Check if content was successfully fetched
        if (!content) {
            console.warn("WikiShield: Killswitch page not found or could not be fetched");
            return killswitch_status;
        }

        const data = JSON.parse(content)?.WikiShield;

        // Update status
        if (typeof data.disabled === 'boolean') {
            killswitch_status.disabled = data.disabled;
        }

        const soft = data.reload?.soft;
        const hard = data.reload?.hard;

        if (startup) {
            if (typeof soft === "number") {
                window.sessionStorage.setItem("WikiShield:SoftReload", soft);
            }
            if (typeof hard === "number") {
                window.sessionStorage.setItem("WikiShield:HardReload", hard);
            }
        } else {
            if (typeof soft === "number") {
                const current = +window.sessionStorage.getItem("WikiShield:SoftReload");
                if (soft > current) {
                    window.sessionStorage.setItem("WikiShield:SoftReload", soft);

                    console.log("WikiShield: Soft reload triggered by killswitch");
                    killswitch_status.alerts.push({
                        id: `app-${performance.now()}`,
                        type: "app",
                        subtype: "soft-reload",
                        timestamp: Date.now(),
                        title: "A newer version of WikiShield has been released!",
                        agent: "WikiShield Development",
                        category: "WikiShield",
                        read: false
                    });
                }
            }
            if (typeof hard === "number") {
                const current = +window.sessionStorage.getItem("WikiShield:HardReload");
                if (hard > current) {
                    window.sessionStorage.setItem("WikiShield:HardReload", hard);
                    window.sessionStorage.setItem("WikiShield:SendHardReloadAlert", true);

                    console.log("WikiShield: Hard reload triggered by killswitch");
                    history.replaceState({ page: "WikiShield-reload" }, "", window.location.href);
                    location.reload();
                }
            }
        }

        return killswitch_status;
    } catch (err) {
        console.error("WikiShield: Failed to check killswitch:", err);
        // Return current state on error (don't disable WikiShield on network failures)
        return killswitch_status;
    }
}

/**
 * Start polling the killswitch page at regular intervals.
 *
 * This function begins a polling loop that checks the killswitch configuration
 * at the interval specified in killswitch_config.
 *
 * @param {WikiShieldAPI} api - The WikiShield API instance for fetching page content
 *
 * @example
 * startKillswitchPolling(wikishield.api);
 */
export function startKillswitchPolling(api, callback) {
    const poll = async () => {
        await checkKillswitch(api, false);

        if (typeof callback === "function") {
            callback(killswitch_status);
        }

        // Schedule next check (only if not force reloaded)
        setTimeout(poll, killswitch_config.polling_interval);
    };

    // Start polling
    poll();
}