Itsnotlupus' React Tools

Observe, inspect and perhaps modify a React tree through the React DevTools Hook

بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @require https://update.greatest.deepsurf.us/scripts/473998/1246974/Itsnotlupus%27%20React%20Tools.js

  1. // ==UserScript==
  2. // @name Itsnotlupus' React Tools
  3. // @description Observe, inspect and perhaps modify a React tree through the React DevTools Hook
  4. // @namespace Itsnotlupus Industries
  5. // @author itsnotlupus
  6. // @license MIT
  7. // @version 1.1
  8. // ==/UserScript==
  9.  
  10. // React shenanigans. the code below allows us to:
  11. // - observe every react renders
  12. // - inspect nodes for props (which can expose redux and other useful state)
  13. // - modify props value (which may not stick unless reapplied on every render)
  14. class ReactTools {
  15.  
  16. #HOOK = '__REACT_DEVTOOLS_GLOBAL_HOOK__';
  17.  
  18. #reactRoots = new Set;
  19. #reactObservers = new Set;
  20. #notifyReactObservers(root) { this.#reactObservers.forEach(fn=>fn(root)); }
  21.  
  22. // singleton, so we only muck with the React hook once.
  23. static #instance;
  24. constructor() {
  25. if (ReactTools.#instance) return ReactTools.#instance;
  26. ReactTools.#instance = this;
  27. const win = globalThis.unsafeWindow ?? window;
  28. const nop = ()=>{};
  29. const hook = win[this.#HOOK];
  30. if (hook) {
  31. const ocfr = hook.onCommitFiberRoot?.bind(hook) ?? nop;
  32. hook.onCommitFiberRoot = (_, root) => {
  33. this.#reactRoots.add(root);
  34. this.#notifyReactObservers(root);
  35. return ocfr(_, root);
  36. };
  37. } else {
  38. win[this.#HOOK] = {
  39. onCommitFiberRoot: (_, root) => {
  40. this.#reactRoots.add(root);
  41. this.#notifyReactObservers(root);
  42. },
  43. onCommitFiberUnmount: nop,
  44. inject: nop,
  45. checkDCE: nop,
  46. supportsFiber: true,
  47. on: nop,
  48. sub: nop,
  49. renderers: [],
  50. emit: nop
  51. };
  52. }
  53. }
  54.  
  55. /** Traversal of React's tree to find nodes that match a props name */
  56. findNodesWithProp(name, firstOnly = false) {
  57. const acc = new Set;
  58. const visited = new Set;
  59. const getPropFromNode = node => {
  60. if (!node || visited.has(node)) return;
  61. visited.add(node);
  62. const props = node.memoizedProps;
  63. if (props && typeof props === 'object' && name in props) {
  64. acc.add(node);
  65. if (firstOnly) throw 0; // goto end
  66. }
  67. getPropFromNode(node.sibling);
  68. getPropFromNode(node.child);
  69. };
  70. try { this.#reactRoots.forEach(root => getPropFromNode(root.current)) } catch {}
  71. return Array.from(acc);
  72. }
  73.  
  74. /** Magically obtain a prop value from the most top-level React component we can find */
  75. getProp(name) {
  76. return this.findNodesWithProp(name, true)[0]?.memoizedProps?.[name];
  77. }
  78.  
  79. /** Forcefully mutate props on a component node in the react tree. */
  80. updateNodeProps(node, props) {
  81. Object.assign(node.memoizedProps, props);
  82. Object.assign(node.pendingProps, props);
  83. Object.assign(node.stateNode?.props??{}, props);
  84. }
  85.  
  86. /** calls a function whenever react renders */
  87. observe(fn) {
  88. this.#reactObservers.add(fn);
  89. return () => this.#reactObservers.delete(fn);
  90. };
  91. // Hook into a React tree, and find a redux-shaped store off of one of the components there.
  92. static withReduxState(fn) {
  93. const react = new ReactTools();
  94. const disconnect = react.observe(() => {
  95. const store = react.getProp('store');
  96. if (store) {
  97. fn(store.getState());
  98. disconnect();
  99. }
  100. });
  101. }
  102. }