JS Cookie Monitor/Debugger Hook

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

目前为 2021-01-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name JS Cookie Monitor/Debugger Hook
  3. // @namespace https://github.com/CC11001100/crawler-js-hook-framework-public
  4. // @version 0.1
  5. // @description 用于监控js对cookie的修改,还可以在修改指定名称的cookie时进入断点
  6. // @author CC11001100
  7. // @match *://*/*
  8. // @run-at document-start
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (() => {
  13.  
  14. // 用于监控cookie的声明周期
  15.  
  16. const debuggerOnCookieChange = ["foobar-cookie-name"];
  17.  
  18. // 使用document.cookie更新cookie,但是cookie新的值和原来的值一样,此时要不要忽略这个事件
  19. const ignoreUpdateButNotChanged = false;
  20.  
  21. addCookieHook();
  22.  
  23. function addCookieHook() {
  24. Object.defineProperty(document, "cookie", {
  25. get: () => {
  26. delete document.cookie;
  27. const currentDocumentCookie = document.cookie;
  28. addCookieHook();
  29. return currentDocumentCookie;
  30. },
  31. set: newValue => {
  32. cc11001100_onSetCookie(newValue);
  33. delete document.cookie;
  34. document.cookie = newValue;
  35. addCookieHook();
  36. },
  37. configurable: true
  38. });
  39. }
  40.  
  41. /**
  42. * 这个方法的前缀起到命名空间的作用,等下调用栈追溯赋值cookie的代码时需要用这个名字作为终结标志
  43. *
  44. * @param newValue
  45. */
  46. function cc11001100_onSetCookie(newValue) {
  47. const cookiePair = parseSetCookie(newValue);
  48.  
  49. // 如果过期时间为当前时间之前,则为删除
  50. if (new Date().getTime() >= cookiePair.expires) {
  51. onDeleteCookie(cookiePair.name);
  52. return;
  53. }
  54. const currentCookieMap = getCurrentCookieMap();
  55. // 如果之前已经存在,则是修改
  56. if (currentCookieMap.has(cookiePair.name)) {
  57. onCookieUpdate(cookiePair.name, currentCookieMap.get(cookiePair.name).value, cookiePair.value);
  58. return;
  59. }
  60.  
  61. // 否则则为添加
  62. onCookieAdd(cookiePair.name, cookiePair.value);
  63. }
  64.  
  65. /**
  66. * 删除cookie
  67. *
  68. * @param cookieName
  69. */
  70. function onDeleteCookie(cookieName) {
  71. const valueStyle = "color: black; background: #E50000; font-size: 13px; font-weight: bold;";
  72. const normalStyle = "color: black; background: #FF6766; font-size: 13px;";
  73.  
  74. const message = [
  75.  
  76. normalStyle,
  77. now(),
  78.  
  79. normalStyle,
  80. "JS Cookie Monitor: ",
  81.  
  82. normalStyle,
  83. "delete cookie, cookieName = ",
  84.  
  85. valueStyle,
  86. `${cookieName}`,
  87.  
  88. normalStyle,
  89. `, code location = ${getCodeLocation()}`
  90. ];
  91. console.log(genFormatArray(message), ...message);
  92.  
  93. if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
  94. debugger;
  95. }
  96. }
  97.  
  98. /**
  99. * 更新cookie
  100. *
  101. * @param cookieName
  102. * @param oldCookieValue
  103. * @param newCookieValue
  104. */
  105. function onCookieUpdate(cookieName, oldCookieValue, newCookieValue) {
  106.  
  107. const cookieValueChanged = oldCookieValue !== newCookieValue;
  108.  
  109. if (ignoreUpdateButNotChanged && !cookieValueChanged) {
  110. return;
  111. }
  112.  
  113. const valueStyle = "color: black; background: #FE9900; font-size: 13px; font-weight: bold;";
  114. const normalStyle = "color: black; background: #FFCC00; font-size: 13px;";
  115.  
  116. const message = [
  117.  
  118. normalStyle,
  119. now(),
  120.  
  121. normalStyle,
  122. "JS Cookie Monitor: ",
  123.  
  124. normalStyle,
  125. "update cookie, name = ",
  126.  
  127. valueStyle,
  128. `${cookieName}`,
  129.  
  130. normalStyle,
  131. `, oldValue = `,
  132.  
  133. valueStyle,
  134. `${oldCookieValue}`,
  135.  
  136. normalStyle,
  137. `, newValue = `,
  138.  
  139. valueStyle,
  140. `${newCookieValue}`,
  141.  
  142. normalStyle,
  143. `, value changed =`,
  144.  
  145. valueStyle,
  146. `${cookieValueChanged}`,
  147.  
  148. normalStyle,
  149. `, code location = ${getCodeLocation()}`
  150. ];
  151. console.log(genFormatArray(message), ...message);
  152.  
  153. if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
  154. debugger;
  155. }
  156. }
  157.  
  158. /**
  159. * 添加cookie
  160. *
  161. * @param cookieName
  162. * @param cookieValue
  163. */
  164. function onCookieAdd(cookieName, cookieValue) {
  165. const valueStyle = "color: black; background: #669934; font-size: 13px; font-weight: bold;";
  166. const normalStyle = "color: black; background: #65CC66; font-size: 13px;";
  167.  
  168. const message = [
  169.  
  170. normalStyle,
  171. now(),
  172.  
  173. normalStyle,
  174. "JS Cookie Monitor: ",
  175.  
  176. normalStyle,
  177. "add cookie, ",
  178.  
  179. valueStyle,
  180. `${cookieName}`,
  181.  
  182. normalStyle,
  183. " = ",
  184.  
  185. valueStyle,
  186. `${cookieValue}`,
  187.  
  188. normalStyle,
  189. `, code location = ${getCodeLocation()}`
  190. ];
  191. console.log(genFormatArray(message), ...message);
  192.  
  193. if (debuggerOnCookieChange.indexOf(cookieName) !== -1) {
  194. debugger;
  195. }
  196. }
  197.  
  198. function now() {
  199. return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", " ") + "] ";
  200. }
  201.  
  202. function genFormatArray(messageAndStyleArray) {
  203. const formatArray = [];
  204. for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
  205. formatArray.push("%c%s");
  206. }
  207. return formatArray.join("");
  208. }
  209.  
  210. function getCodeLocation() {
  211. const callstack = new Error().stack.split("\n");
  212. while (callstack.length && callstack[0].indexOf("cc11001100") === -1) {
  213. callstack.shift();
  214. }
  215. callstack.shift();
  216. callstack.shift();
  217.  
  218. return callstack[0].trim();
  219. }
  220.  
  221. /**
  222. * 将本次设置cookie的字符串解析为容易处理的形式
  223. *
  224. * @param cookieString
  225. * @returns {CookiePair}
  226. */
  227. function parseSetCookie(cookieString) {
  228. // uuid_tt_dd=10_37476713480-1609821005397-659114; Expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; Domain=.csdn.net;
  229. const cookieStringSplit = cookieString.split(";");
  230. const cookieNameValueArray = cookieStringSplit[0].split("=");
  231. const cookieName = decodeURIComponent(cookieNameValueArray[0].trim());
  232. const cookieValue = cookieNameValueArray.length > 1 ? decodeURIComponent(cookieNameValueArray[1].trim()) : "";
  233. const map = new Map();
  234. for (let i = 1; i < cookieStringSplit.length; i++) {
  235. const ss = cookieStringSplit[i].split("=", 2);
  236. const key = ss[0].trim().toLowerCase();
  237. const value = ss.length > 1 ? ss[1].trim() : "";
  238. map.set(key, value);
  239. }
  240. const expires = map.get("expires");
  241. return new CookiePair(cookieName, cookieValue, new Date(expires).getTime())
  242. }
  243.  
  244. /**
  245. * 获取当前所有已经设置的cookie
  246. *
  247. * @returns {Map<string, CookiePair>}
  248. */
  249. function getCurrentCookieMap() {
  250. const cookieMap = new Map();
  251. if (!document.cookie) {
  252. return cookieMap;
  253. }
  254. document.cookie.split(";").forEach(x => {
  255. const ss = x.split("=", 2);
  256. const key = decodeURIComponent(ss[0].trim());
  257. const value = ss.length > 1 ? decodeURIComponent(ss[1].trim()) : "";
  258. cookieMap.set(key, new CookiePair(key, value));
  259. });
  260. return cookieMap;
  261. }
  262.  
  263. class CookiePair {
  264. constructor(name, value, expires) {
  265. this.name = name;
  266. this.value = value;
  267. this.expires = expires;
  268. }
  269. }
  270.  
  271. })();