JS Cookie Monitor/Debugger Hook

用于监控js对cookie的修改,还可以在修改指定名称的cookie时进入断点

As of 07. 01. 2021. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         JS Cookie Monitor/Debugger Hook
// @namespace    https://github.com/CC11001100/crawler-js-hook-framework-public
// @version      0.1
// @description  用于监控js对cookie的修改,还可以在修改指定名称的cookie时进入断点
// @author       CC11001100
// @match       *://*/*
// @run-at      document-start
// @grant       none
// ==/UserScript==

(() => {

    // 用于监控cookie的声明周期

    const debuggerOnCookieChange = ["foobar-cookie-name"];

    // 使用document.cookie更新cookie,但是cookie新的值和原来的值一样,此时要不要忽略这个事件
    const ignoreUpdateButNotChanged = false;

    addCookieHook();

    function addCookieHook() {
        Object.defineProperty(document, "cookie", {
            get: () => {
                delete document.cookie;
                const currentDocumentCookie = document.cookie;
                addCookieHook();
                return currentDocumentCookie;
            },
            set: newValue => {
                cc11001100_onSetCookie(newValue);
                delete document.cookie;
                document.cookie = newValue;
                addCookieHook();
            },
            configurable: true
        });
    }

    /**
     * 这个方法的前缀起到命名空间的作用,等下调用栈追溯赋值cookie的代码时需要用这个名字作为终结标志
     *
     * @param newValue
     */
    function cc11001100_onSetCookie(newValue) {
        const cookiePair = parseSetCookie(newValue);

        // 如果过期时间为当前时间之前,则为删除
        if (new Date().getTime() >= cookiePair.expires) {
            onDeleteCookie(cookiePair.name);
            return;
        }
        const currentCookieMap = getCurrentCookieMap();
        // 如果之前已经存在,则是修改
        if (currentCookieMap.has(cookiePair.name)) {
            onCookieUpdate(cookiePair.name, currentCookieMap.get(cookiePair.name).value, cookiePair.value);
            return;
        }

        // 否则则为添加
        onCookieAdd(cookiePair.name, cookiePair.value);
    }

    /**
     * 删除cookie
     *
     * @param cookieName
     */
    function onDeleteCookie(cookieName) {
        const valueStyle = "color: black; background: #E50000; font-size: 13px; font-weight: bold;";
        const normalStyle = "color: black; background: #FF6766; font-size: 13px;";

        const message = [

            normalStyle,
            now(),

            normalStyle,
            "JS Cookie Monitor: ",

            normalStyle,
            "delete cookie, cookieName = ",

            valueStyle,
            `${cookieName}`,

            normalStyle,
            `, code location = ${getCodeLocation()}`
        ];
        console.log(genFormatArray(message), ...message);

        if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
            debugger;
        }
    }

    /**
     * 更新cookie
     *
     * @param cookieName
     * @param oldCookieValue
     * @param newCookieValue
     */
    function onCookieUpdate(cookieName, oldCookieValue, newCookieValue) {

        const cookieValueChanged = oldCookieValue !== newCookieValue;

        if (ignoreUpdateButNotChanged && !cookieValueChanged) {
            return;
        }

        const valueStyle = "color: black; background: #FE9900; font-size: 13px; font-weight: bold;";
        const normalStyle = "color: black; background: #FFCC00; font-size: 13px;";

        const message = [

            normalStyle,
            now(),

            normalStyle,
            "JS Cookie Monitor: ",

            normalStyle,
            "update cookie, name = ",

            valueStyle,
            `${cookieName}`,

            normalStyle,
            `, oldValue = `,

            valueStyle,
            `${oldCookieValue}`,

            normalStyle,
            `, newValue = `,

            valueStyle,
            `${newCookieValue}`,

            normalStyle,
            `, value changed =`,

            valueStyle,
            `${cookieValueChanged}`,

            normalStyle,
            `, code location = ${getCodeLocation()}`
        ];
        console.log(genFormatArray(message), ...message);

        if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
            debugger;
        }
    }

    /**
     * 添加cookie
     *
     * @param cookieName
     * @param cookieValue
     */
    function onCookieAdd(cookieName, cookieValue) {
        const valueStyle = "color: black; background: #669934; font-size: 13px; font-weight: bold;";
        const normalStyle = "color: black; background: #65CC66; font-size: 13px;";

        const message = [

            normalStyle,
            now(),

            normalStyle,
            "JS Cookie Monitor: ",

            normalStyle,
            "add cookie, ",

            valueStyle,
            `${cookieName}`,

            normalStyle,
            " = ",

            valueStyle,
            `${cookieValue}`,

            normalStyle,
            `, code location = ${getCodeLocation()}`
        ];
        console.log(genFormatArray(message), ...message);

        if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
            debugger;
        }
    }

    function now() {
        return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", " ") + "] ";
    }

    function genFormatArray(messageAndStyleArray) {
        const formatArray = [];
        for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
            formatArray.push("%c%s");
        }
        return formatArray.join("");
    }

    function getCodeLocation() {
        const callstack = new Error().stack.split("\n");
        while (callstack.length && callstack[0].indexOf("cc11001100") === -1) {
            callstack.shift();
        }
        callstack.shift();
        callstack.shift();

        return callstack[0].trim();
    }

    /**
     * 将本次设置cookie的字符串解析为容易处理的形式
     *
     * @param cookieString
     * @returns {CookiePair}
     */
    function parseSetCookie(cookieString) {
        // uuid_tt_dd=10_37476713480-1609821005397-659114; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
        const cookieStringSplit = cookieString.split(";");
        const cookieNameValueArray = cookieStringSplit[0].split("=");
        const cookieName = decodeURIComponent(cookieNameValueArray[0].trim());
        const cookieValue = cookieNameValueArray.length > 1 ? decodeURIComponent(cookieNameValueArray[1].trim()) : "";
        const map = new Map();
        for (let i = 1; i < cookieStringSplit.length; i++) {
            const ss = cookieStringSplit[i].split("=", 2);
            const key = ss[0].trim().toLowerCase();
            const value = ss.length > 1 ? ss[1].trim() : "";
            map.set(key, value);
        }
        const expires = map.get("expires");
        return new CookiePair(cookieName, cookieValue, new Date(expires).getTime())
    }

    /**
     * 获取当前所有已经设置的cookie
     *
     * @returns {Map<string, CookiePair>}
     */
    function getCurrentCookieMap() {
        const cookieMap = new Map();
        if (!document.cookie) {
            return cookieMap;
        }
        document.cookie.split(";").forEach(x => {
            const ss = x.split("=", 2);
            const key = decodeURIComponent(ss[0].trim());
            const value = ss.length > 1 ? decodeURIComponent(ss[1].trim()) : "";
            cookieMap.set(key, new CookiePair(key, value));
        });
        return cookieMap;
    }

    class CookiePair {
        constructor(name, value, expires) {
            this.name = name;
            this.value = value;
            this.expires = expires;
        }
    }

})();