您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
用于监控js对localStorage/sessionStorage的任何操作,或者在符合给定条件时进入断点
// ==UserScript== // @name Storage Monitor/Debugger Hook // @namespace https://github.com/CC11001100/crawler-js-hook-framework-public/tree/master/005-storage-hook // @version 0.1 // @description 用于监控js对localStorage/sessionStorage的任何操作,或者在符合给定条件时进入断点 // @document https://github.com/CC11001100/crawler-js-hook-framework-public/tree/master/005-storage-hook // @author CC11001100 // @match *://*/* // @run-at document-start // @grant none // ==/UserScript== (() => { // 简介: 用于检测、调试浏览器中的localStorage和sessionStorage的任何操作 // 本工具详细文档见: // Storage是什么: https://developer.mozilla.org/zh-CN/docs/Web/API/Storage // 修改这里来打断点 const storageDebuggerList = [ "947e722bbefb8a455c278113042beadb", // 允许使用字符串,字符串用来对name做完全相等的匹配 // 对LocalStorage或SessionStorage的key为foo-name进行的任何操作都会进入断点 // "foo-name", // 字符串形式的增强版,允许使用正则表达式,正则表达式只用来匹配name // /^foo-prefix*/, // 这才是一个完整的配置,可以比较精细的打断点 // { // // // storageType { "local" | "session" | "all" } // "storageType": "all", // // // operationType { "get" | "set" | "remove" | "clear" | "key" | "all" } // "operationType": "all", // // // nameFilter { "string" | RegExp | null } // "nameFilter": "foo-name", // // // valueFilter { "string" | RegExp | null } // "valueFilter": "foo-value" // // } ] // 可以禁用storage来辅助调试,不需要每次都去傻啦吧唧的删除,让它写不进去读不出来即可 // 可以这样同时控制localStorage和sessionStorage是否可读和可写 const enableStorage = { read: true, write: true } // 支持的另一种配置方式: // 也可以精确的为每一个类型指定可读和可写 // const enableStorage = { // localStorage: { // // localStorage是否是可读的 // read: true, // // localStorage是否是可写的 // write: true // }, // sessionStorage: { // // sessionStorage是否是可读的 // read: true, // // sessionStorage是否是可写的 // write: true // } // } // 在控制台打印日志时字体大小,根据自己喜好调整 // 众所周知,12px是宇宙通用大小 const consoleLogFontSize = 12; // --------------------------------- 以下为程序内部逻辑,可忽略 --------------------------------------------- // 防止重复注入 const _cc11001100_hook_storage = window._cc11001100_hook_storage = window._cc11001100_hook_storage || {}; if ("isInjectHook" in _cc11001100_hook_storage) { return } _cc11001100_hook_storage["isInjectHook"] = true addHook("session", window.sessionStorage); addHook("local", window.localStorage); /** * 为一个storage对象添加Hook,可以是localStorage或者sessionStorage * * @param storageTypeName { "local" | "session" } * @param storageObject { window.localStorage | window.sessionStorage} */ function addHook(storageTypeName, storageObject) { // getItem const storageGetItem = storageObject.getItem; storageObject.getItem = function (itemName) { const itemValue = storageGetItem.apply(this, [itemName]); const valueStyle = `color: black; background: #85C1E9; font-size: ${consoleLogFontSize}px; font-weight: bold;`; const normalStyle = `color: black; background: #D6EAF8; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, "get", normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, ", name = ", valueStyle, `${itemName}`, normalStyle, ", value = ", valueStyle, `${itemValue}`, normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); testStorageDebugger(storageTypeName, "get", itemName, itemValue); // 如果关闭读功能的话,则阻止其能够读到值 if (!isStorageEnable(storageTypeName, "read")) { const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", normalStyle, "ignore ", valueStyle, "get", normalStyle, ` because disable `, valueStyle, `${storageTypeName}`, normalStyle, " ", valueStyle, "read", normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); return null; } return itemValue; } storageObject.getItem.toString = () => "function getItem() { [native code] }"; // setItem const storageSetItem = storageObject.setItem; storageObject.setItem = function (itemName, itemValue) { const oldValue = storageGetItem.apply(this, [itemName]); let valueStyle = ""; let normalStyle = ""; if (oldValue == null) { // 认为是新增 valueStyle = `color: black; background: #669934; font-size: ${consoleLogFontSize}px; font-weight: bold;`; normalStyle = `color: black; background: #65CC66; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, "set", normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, ", name = ", valueStyle, `${itemName}`, normalStyle, ", value = ", valueStyle, `${itemValue}`, normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); } else { // 认为是修改 valueStyle = `color: black; background: #FE9900; font-size: ${consoleLogFontSize}px; font-weight: bold;`; normalStyle = `color: black; background: #FFCC00; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, "set", normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, ", name = ", valueStyle, `${itemName}`, normalStyle, ", newValue = ", valueStyle, `${itemValue}`, ...(() => { if (oldValue === itemValue) { // 值没有发生改变 return [ normalStyle, ", value changed = ", valueStyle, `false` ] } else { // 值发生了改变 return [ normalStyle, ", oldValue = ", valueStyle, `${oldValue}`, normalStyle, ", value changed = ", valueStyle, `true` ] } })(), normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); } testStorageDebugger(storageTypeName, "set", itemName, itemValue); // 如果关闭写功能的话,则阻止其能够修改值 if (!isStorageEnable(storageTypeName, "write")) { const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", normalStyle, "ignore ", valueStyle, "set", normalStyle, ` because disable `, valueStyle, `${storageTypeName}`, normalStyle, " ", valueStyle, "write", normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); return null; } return storageSetItem.apply(this, [itemName, itemValue]); } storageObject.setItem.toString = () => "function setItem() { [native code] }"; // removeItem const storageRemoveItem = storageObject.removeItem; storageObject.removeItem = function (itemName) { const oldValue = storageGetItem.apply(this, [itemName]); const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, "remove", normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, ", name = ", valueStyle, `${itemName}`, normalStyle, ", value = ", valueStyle, `${oldValue}`, normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); testStorageDebugger(storageTypeName, "remove", itemName, null); // 如果关闭写功能的话,则阻止其能够修改值 if (!isStorageEnable(storageTypeName, "write")) { const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", normalStyle, "ignore ", valueStyle, "remove", normalStyle, ` because disable `, valueStyle, `${storageTypeName}`, normalStyle, " ", valueStyle, "write", normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); return null; } return storageRemoveItem.apply(this, [itemName]); } storageObject.removeItem.toString = () => "function removeItem() { [native code] }"; // clear const storageClear = storageObject.clear; storageObject.clear = function () { const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, "clear", normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); testStorageDebugger(storageTypeName, "clear", null, null); // 如果关闭写功能的话,则阻止其能够修改值 if (!isStorageEnable(storageTypeName, "write")) { const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", normalStyle, "ignore ", valueStyle, "clear", normalStyle, ` because disable `, valueStyle, `${storageTypeName}`, normalStyle, " ", valueStyle, "write", normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); return null; } return storageClear.apply(this); } storageObject.clear.toString = () => "function clear() { [native code] }"; // key const storageKey = storageObject.key; storageObject.key = function (itemIndex) { const value = storageKey.apply(this, [itemIndex]); const valueStyle = `color: black; background: #85C1E9; font-size: ${consoleLogFontSize}px; font-weight: bold;`; const normalStyle = `color: black; background: #D6EAF8; font-size: ${consoleLogFontSize}px;`; const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", valueStyle, `key`, normalStyle, " ", valueStyle, `${storageTypeName} storage`, normalStyle, `, itemIndex = `, valueStyle, `${itemIndex}`, normalStyle, ", value = ", valueStyle, `${value}`, normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); testStorageDebugger(storageTypeName, "key", null, value); // 如果关闭读功能的话,则阻止其能够读到值 if (!isStorageEnable(storageTypeName, "read")) { const message = [ normalStyle, now(), normalStyle, "Storage Monitor: ", normalStyle, "ignore ", valueStyle, "key", normalStyle, ` because disable `, valueStyle, `${storageTypeName}`, normalStyle, " ", valueStyle, "read", normalStyle, `, code location = ${cc11001100_getCodeLocation()}` ]; console.log(genFormatArray(message), ...message); return null; } return value; } storageObject.key.toString = () => "function key() { [native code] }"; } /** * 对应类型的storage是否开启 * * @param storageTypeName { "local" | "session" } * @param operationType { "read" | "write" } */ function isStorageEnable(storageTypeName, operationType) { if (storageTypeName === "local") { return enableStorage["localStorage"][operationType] } else if (storageTypeName === "session") { return enableStorage["sessionStorage"][operationType] } else { return true } } /** * 测试是否要进入断点 * * @param storageType { "local" | "session" | "all" } * @param operationType { "get" | "set" | "remove" | "clear" | "key" | "all" } * @param name { "string" | null } * @param value { "string" | null } */ function testStorageDebugger(storageType, operationType, name, value) { for (let storageDebugger of storageDebuggerList) { // 将鼠标移动到这里在变量上悬停查看其值,能够知道是命中了什么规则 if (storageDebugger.testDebugger(storageType, operationType, name, value)) { debugger; } } } // 断点规则 class StorageDebugger { /** * * @param storageType { "local" | "session" | "all" } * @param operationType { "get" | "set" | "remove" | "clear" | "key" | "all" } * @param nameFilter { "string" | RegExp | null } * @param valueFilter { "string" | RegExp | null } */ constructor(storageType, operationType, nameFilter, valueFilter) { this.storageType = storageType; this.operationType = operationType; this.nameFilter = nameFilter; this.valueFilter = valueFilter; } testDebugger(storageType, operationType, name, value) { if (!this.testByStorageType(storageType)) { return false } if (!this.testByOperationType(operationType)) { return false } if (this.nameFilter && !this.testByName(name)) { return false; } if (this.valueFilter && !this.testByValue(value)) { return false; } return true; } testByStorageType(storageType) { if (storageType === "all" || this.storageType === "all") { return true } return this.storageType === storageType; } testByOperationType(operationType) { if (operationType === "all" || this.operationType === "all") { return true } return this.operationType === operationType; } testByName(name) { if (!this.nameFilter) { return false } if (!name) { return false } if (typeof this.nameFilter === "string") { return this.nameFilter === name; } else if (typeof this.nameFilter instanceof RegExp) { return this.nameFilter.test(name) } else { return false; } } testByValue(value) { if (!this.valueFilter) { return false } if (!value) { return false } if (typeof this.valueFilter === "string") { return this.valueFilter === value; } else if (typeof this.valueFilter instanceof RegExp) { return this.valueFilter.test(value) } else { return false; } } } // 把storage的读写属性统一,方便后面程序处理 (function convertEnableStorage() { // 设置默认值 enableStorage["localStorage"] = enableStorage["localStorage"] || {} enableStorage["sessionStorage"] = enableStorage["sessionStorage"] || {} // 扩展read if ("read" in enableStorage) { enableStorage["localStorage"]["read"] = enableStorage["read"] enableStorage["sessionStorage"]["read"] = enableStorage["read"] delete enableStorage["read"] } // 扩展write if ("write" in enableStorage) { enableStorage["localStorage"]["write"] = enableStorage["write"] enableStorage["sessionStorage"]["write"] = enableStorage["write"] delete enableStorage["write"] } // 如果没有配置的话,则设置默认值 if (!("write" in enableStorage["localStorage"])) { enableStorage["localStorage"]["write"] = true } if (!("read" in enableStorage["localStorage"])) { enableStorage["localStorage"]["read"] = true } if (!("write" in enableStorage["sessionStorage"])) { enableStorage["sessionStorage"]["write"] = true } if (!("read" in enableStorage["sessionStorage"])) { enableStorage["sessionStorage"]["read"] = true } })(); // 把storage的断点规则转换为程序内部使用的格式 (function convertStorageDebugger() { // const valueStyle = `color: black; background: #FF2121; font-size: ${Math.round(consoleLogFontSize * 1.5)}px; font-weight: bold;`; const normalStyle = `color: black; background: #FF2121; font-size: ${Math.round(consoleLogFontSize * 1.5)}px;`; const newStorageDebuggerList = []; for (let x of storageDebuggerList) { if (typeof x === "string" || x instanceof RegExp) { // 如果设置的是名字,则只针对按名称操作的操作打断点 newStorageDebuggerList.push(new StorageDebugger("all", "get", x, null)); newStorageDebuggerList.push(new StorageDebugger("all", "set", x, null)); newStorageDebuggerList.push(new StorageDebugger("all", "remove", x, null)); } else { // 检查设置项的合法性 if ("storageType" in x && ["local", "session", "all"].indexOf(x["storageType"].toLowerCase()) === -1) { const message = [ normalStyle, `${now()} Storage Monitor: storageType error, value = ${x["storageType"]}, need to be = { "local", "session", "all" }, so ignore this debugger = ${JSON.stringify(x)}`, ]; console.log(genFormatArray(message), ...message); continue } if ("operationType" in x && ["get", "set", "remove", "clear", "key", "all"].indexOf(x["operationType"].toLowerCase()) === -1) { const message = [ normalStyle, `${now()} Storage Monitor: storageType error, value = ${x["operationType"]}, need to be { "get" | "set" | "remove" | "clear" | "key" | "all" }, so ignore this debugger = ${JSON.stringify(x)}`, ]; console.log(genFormatArray(message), ...message); continue } if (["nameFilter"] in x && (typeof x["nameFilter"] != "string") && !(x["nameFilter"] instanceof RegExp)) { const message = [ normalStyle, `${now()} Storage Monitor: nameFilter config error, value = ${x["nameFilter"]}, need to be { string | Regexp | null }, so ignore this debugger = ${JSON.stringify(x)}`, ]; console.log(genFormatArray(message), ...message); continue } if (["valueFilter"] in x && (typeof x["valueFilter"] != "string") && !(x["valueFilter"] instanceof RegExp)) { const message = [ normalStyle, `${now()} Storage Monitor: valueFilter config error, value = ${x["valueFilter"]}, need to be { string | Regexp | null }, so ignore this debugger = ${JSON.stringify(x)}`, ]; console.log(genFormatArray(message), ...message); continue } // TODO 出现了其它类型的key,是否配置错误呢? const storageType = x["storageType"] || "all"; if ((x["name"] || x["value"]) && x["operationType"]) { const name = x["name"] || null; const value = x["value"] || null; newStorageDebuggerList.push(new StorageDebugger("all", "get", name, value)); newStorageDebuggerList.push(new StorageDebugger("all", "set", name, value)); newStorageDebuggerList.push(new StorageDebugger("all", "remove", name, value)); } else { const operationType = x["operationType"] || "all"; const name = x["name"] || null; const value = x["value"] || null; newStorageDebuggerList.push(new StorageDebugger(storageType, operationType, name, value)); } } } // 把原来的规则替换掉 while (storageDebuggerList.pop()) { } for (let x of newStorageDebuggerList) { storageDebuggerList.push(x); } })(); // 奇奇怪怪的模板方式竟然一路被沿用下来...(*/ω\*) function genFormatArray(messageAndStyleArray) { const formatArray = []; for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) { formatArray.push("%c%s"); } return formatArray.join(""); } function now() { // 东八区专属... return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", "") + "] "; } function cc11001100_getCodeLocation() { const callstack = new Error().stack.split("\n"); while (callstack.length && callstack[0].indexOf("cc11001100_getCodeLocation") === -1) { callstack.shift(); } callstack.shift(); callstack.shift(); return callstack[0].trim(); } })();