3

Description

Websockets on Stack Exchange provide live updates on a page - for example, showing there are new notifications, or new answers, or new comments, etc.

These are unreliable in some cases. If the websocket dies, no live updates would show up. If you experience this, try this userscript. It will simulate activity through the websocket by sending a periodic ping. This can be enough to protect from a disconnect.

Known issues

  • It is not a silver bullet - there could be firewall or a network device somewhere that forces the disconnect despite there being activity.
  • "debibrilator" is the wrong spelling of "defibrillator"

Configuration

The script does not require setting any configuration to work. It is only there for possible advance uses. See the documentation here if needed.

Installation

Direct install (GitHub)

See the code on GitHub

(For instructions how to install userscripts: see the tag wiki here)

Code:

// ==UserScript==
// @name          Stack Exchange websocket defibrilator
// @description   Tries to keep socket connections alive by doing periodic pings
// @author        VLAZ
// @grant         none
// @inject-into   page
// @match         https://stackoverflow.com/*
// @match         https://serverfault.com/*
// @match         https://superuser.com/*
// @match         https://*.stackexchange.com/*
// @match         https://askubuntu.com/*
// @match         https://stackapps.com/*
// @match         https://mathoverflow.net/*
// @match         https://pt.stackoverflow.com/*
// @match         https://ja.stackoverflow.com/*
// @match         https://ru.stackoverflow.com/*
// @match         https://es.stackoverflow.com/*
// @match         https://meta.stackoverflow.com/*
// @match         https://meta.serverfault.com/*
// @match         https://meta.superuser.com/*
// @match         https://meta.askubuntu.com/*
// @match         https://meta.mathoverflow.net/*
// @match         https://pt.meta.stackoverflow.com/*
// @match         https://ja.meta.stackoverflow.com/*
// @match         https://ru.meta.stackoverflow.com/*
// @match         https://es.meta.stackoverflow.com/*
// @namespace     https://github.com/PurpleMagick/
// @run-at        document-start
// @version       1.0.0
// ==/UserScript==
(() => {
    "use strict";
    const LoggingLevel = {
        OFF     : 0,
        ERROR   : 1,
        WARNING : 2,
        INFO    : 3,
        LOG     : 3,
        DEBUG   : 4,
        TRACE   : 5,
        ALL     : 99,
    };
    const configLogLevel = readConfig("log_level");
    
    const LOG_LEVEL = LoggingLevel[configLogLevel] ?? LoggingLevel.OFF;
    const PING_INTERVAL_IN_SECONDS = readConfig("ping_interval_in_seconds") ?? 10;
        
    const sockets = new Set();

    let lastPing = Date.now();
    
    //heartbeat pulse
    setInterval(() => {
        const elapsedSecondsSinceLastPing = (Date.now() - lastPing) / 1000;
        const elapsedSecondsSinceLastPingFormatted = elapsedSecondsSinceLastPing.toFixed(2);

        if (elapsedSecondsSinceLastPing < PING_INTERVAL_IN_SECONDS){
            if (LOG_LEVEL >= LoggingLevel.TRACE)
                console.debug(`${elapsedSecondsSinceLastPingFormatted}s elapsed since last ping. The minimal interval is ${PING_INTERVAL_IN_SECONDS}s. Skipping ping.`);
            return;
        }

        if (LOG_LEVEL >= LoggingLevel.DEBUG)
            console.debug(`${elapsedSecondsSinceLastPingFormatted}s elapsed since last ping. The minimal interval is ${PING_INTERVAL_IN_SECONDS}s: pinging ${sockets.size} sockets`);

        for (const socket of sockets) {
            if(socket.readyState === 1)
                socket.send("");
            else
                if (LOG_LEVEL >= LoggingLevel.WARNING)
                    console.warn(`skipping ping to websocket in readyState [${socket.readyState}]`);

        }
        lastPing = Date.now();
    }, 1000);

    const OrigWebSocket = window.WebSocket;

    //monkeypatch the websocket creation
    window.WebSocket = function () {
        const socket = new OrigWebSocket(...arguments);

        sockets.add(socket);

        if (LOG_LEVEL >= LoggingLevel.LOG)
                console.log("websocket created");

        socket.addEventListener("close", (e) => {
            if (LOG_LEVEL >= LoggingLevel.LOG)
                console.log("websocket closed", e);

            sockets.delete(socket);
        });

        return socket;
    };
    
    function readConfig(key) {
        try {
            const configValue = localStorage.getItem(`WebSocketDefibrilator:${key}`);
            return JSON.parse(configValue) ?? null;
        } catch(e) { /* ignore */ }
        return null;
    }
})();

0

You must log in to answer this question.

Browse other questions tagged .