Itsnotlupus' React Tools

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

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/473998/1246974/Itsnotlupus%27%20React%20Tools.js

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name        Itsnotlupus' React Tools
// @description Observe, inspect and perhaps modify a React tree through the React DevTools Hook
// @namespace   Itsnotlupus Industries
// @author      itsnotlupus
// @license     MIT
// @version     1.1
// ==/UserScript==

// React shenanigans. the code below allows us to:
// - observe every react renders
// - inspect nodes for props (which can expose redux and other useful state)
// - modify props value (which may not stick unless reapplied on every render)
class ReactTools {

  #HOOK = '__REACT_DEVTOOLS_GLOBAL_HOOK__';

  #reactRoots = new Set;
  #reactObservers = new Set;
  #notifyReactObservers(root) { this.#reactObservers.forEach(fn=>fn(root)); }

  // singleton, so we only muck with the React hook once.
  static #instance;
  constructor() {
    if (ReactTools.#instance) return ReactTools.#instance;
    ReactTools.#instance = this;
    
    const win = globalThis.unsafeWindow ?? window;
    const nop = ()=>{};
    const hook = win[this.#HOOK];
    if (hook) {
      const ocfr = hook.onCommitFiberRoot?.bind(hook) ?? nop;
      hook.onCommitFiberRoot = (_, root) => {
        this.#reactRoots.add(root);
        this.#notifyReactObservers(root);
        return ocfr(_, root);
      };
    } else {
      win[this.#HOOK] = {
        onCommitFiberRoot: (_, root) => {
          this.#reactRoots.add(root);
          this.#notifyReactObservers(root);
        },
        onCommitFiberUnmount: nop,
        inject: nop,
        checkDCE: nop,
        supportsFiber: true,
        on: nop,
        sub: nop,
        renderers: [],
        emit: nop
      };
    }
  }

  /** Traversal of React's tree to find nodes that match a props name */
  findNodesWithProp(name, firstOnly = false) {
    const acc = new Set;
    const visited = new Set;
    const getPropFromNode = node => {
      if (!node || visited.has(node)) return;
      visited.add(node);
      const props = node.memoizedProps;
      if (props && typeof props === 'object' && name in props) {
        acc.add(node);
        if (firstOnly) throw 0; // goto end
      }
      getPropFromNode(node.sibling);
      getPropFromNode(node.child);
    };
    try { this.#reactRoots.forEach(root => getPropFromNode(root.current)) } catch {}
    return Array.from(acc);
  }

  /** Magically obtain a prop value from the most top-level React component we can find */
  getProp(name) {
    return this.findNodesWithProp(name, true)[0]?.memoizedProps?.[name];
  }

  /** Forcefully mutate props on a component node in the react tree. */
  updateNodeProps(node, props) {
    Object.assign(node.memoizedProps, props);
    Object.assign(node.pendingProps, props);
    Object.assign(node.stateNode?.props??{}, props);
  }

  /** calls a function whenever react renders */
  observe(fn) {
    this.#reactObservers.add(fn);
    return () => this.#reactObservers.delete(fn);
  };
  
  // Hook into a React tree, and find a redux-shaped store off of one of the components there.
  static withReduxState(fn) {
    const react = new ReactTools();
    const disconnect = react.observe(() => {
      const store = react.getProp('store');
      if (store) {
        fn(store.getState());
        disconnect();
      }
    });
  }
}