Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dougwithseismic/156ae2d424e687d257630cd92aaf00d6 to your computer and use it in GitHub Desktop.
Save dougwithseismic/156ae2d424e687d257630cd92aaf00d6 to your computer and use it in GitHub Desktop.
Start Reversing Literally ANY React App
// Configuration object for the search
const searchConfig = {
searchCriteria: { user: { id: true, email: true } }, // What we're looking for
maxDepth: 50, // How deep to search in the tree
stopAfterFirst: false, // Whether to stop after finding the first match
searchPaths: ["memoizedProps", "memoizedState"], // Where to look in each node
mainSelector: "#__next", // The root element of our React app
callback: (matchingObjects) => {
matchingObjects.forEach(({ matchingObject, fiberNode }) => {
console.log("Found matching object:", matchingObject);
console.log(fiberNode)
// Uncomment the next line to force a rerender on each match
// forceRerender(fiberNode);
});
}
};
// Main function to search the React Fiber tree
function searchReactFiber(config) {
const results = [];
const visitedNodes = new WeakSet();
// Helper function to safely access object properties
function safelyAccessProperty(obj, prop) {
try {
return obj[prop];
} catch (error) {
if (error instanceof DOMException && error.name === "SecurityError") {
return null; // Silently skip security-restricted properties
}
throw error;
}
}
// Check if an object matches our search criteria
function isMatchingObject(obj, criteria) {
if (typeof obj !== "object" || obj === null) return false;
for (const [key, value] of Object.entries(criteria)) {
const objValue = safelyAccessProperty(obj, key);
if (objValue === null) return false;
if (typeof value === "object" && value !== null) {
if (!isMatchingObject(objValue, value)) return false;
} else if (value === true) {
if (objValue === undefined) return false;
} else {
if (objValue !== value) return false;
}
}
return true;
}
// Traverse the Fiber tree
function traverseFiberTree(startNode) {
const stack = [{ node: startNode, depth: 0 }];
while (stack.length > 0) {
const { node, depth } = stack.pop();
if (!node || typeof node !== "object" || depth > config.maxDepth || visitedNodes.has(node)) {
continue;
}
visitedNodes.add(node);
// Check searchPaths for matching objects
for (const propName of config.searchPaths) {
const propValue = safelyAccessProperty(node, propName);
if (propValue && typeof propValue === "object") {
if (isMatchingObject(propValue, config.searchCriteria)) {
results.push({ matchingObject: propValue, fiberNode: node });
if (config.stopAfterFirst) return;
}
// Search nested objects in memoizedProps and memoizedState
if (propName === "memoizedProps" || propName === "memoizedState") {
searchNestedObjects(propValue, node);
}
}
}
// Add child and sibling nodes to the stack
const child = safelyAccessProperty(node, 'child');
if (child) stack.push({ node: child, depth: depth + 1 });
const sibling = safelyAccessProperty(node, 'sibling');
if (sibling) stack.push({ node: sibling, depth });
}
}
// Search nested objects within a node
function searchNestedObjects(obj, fiberNode) {
const stack = [obj];
const visited = new WeakSet();
while (stack.length > 0) {
const current = stack.pop();
if (typeof current !== "object" || current === null || visited.has(current)) {
continue;
}
visited.add(current);
if (isMatchingObject(current, config.searchCriteria)) {
results.push({ matchingObject: current, fiberNode });
if (config.stopAfterFirst) return;
}
// Search keys that are likely to contain relevant data
const keysToSearch = Object.keys(current).filter(key =>
typeof current[key] === "object" &&
current[key] !== null &&
!Array.isArray(current[key]) &&
key !== "$$typeof" &&
!key.startsWith("_")
);
for (const key of keysToSearch) {
const value = safelyAccessProperty(current, key);
if (value !== null) stack.push(value);
}
}
}
// Get the root fiber node
const main = document.querySelector(config.mainSelector);
if (!main) {
console.warn(`Main element not found with selector: ${config.mainSelector}`);
return results;
}
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
if (!fiberKey) {
console.warn("React fiber key not found. This may not be a React application or the fiber structure has changed.");
return results;
}
const fiberNode = safelyAccessProperty(main, fiberKey);
if (!fiberNode) {
console.warn("Unable to access fiber node. Skipping search.");
return results;
}
// Start the search
traverseFiberTree(fiberNode);
// Call the callback function if provided
if (typeof config.callback === 'function') {
config.callback(results);
}
return results;
}
// Helper function to force rerender a component
function forceRerender(fiber) {
while (fiber && !fiber.stateNode?.forceUpdate) {
fiber = fiber.return;
}
if (fiber && fiber.stateNode) {
fiber.stateNode.forceUpdate();
}
}
// Execute the search
console.time("Search time");
const matchingObjects = searchReactFiber(searchConfig);
console.timeEnd("Search time");
console.log(`Found ${matchingObjects.length} matching objects`);
// Get the root fiber node
const main = document.querySelector("#__next");
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
const fiberNode = main[fiberKey];
const visitedNodes = new WeakSet();
const PATCH_SYMBOL = Symbol('WS_PATCHED');
function isObjectOrArray(value) {
return (typeof value === "object" && value !== null) || Array.isArray(value);
}
function isStyleObject(obj) {
try {
return obj && typeof obj === "object" && Object.keys(obj).some((key) =>
["color", "fontSize", "margin", "padding", "display", "position"].includes(key)
);
} catch (error) {
console.warn("Error in isStyleObject:", error);
return false;
}
}
function isPromise(p) {
return p && typeof p.then === 'function';
}
function logPromiseFunctions(obj) {
if (!obj || typeof obj !== "object" || visitedNodes.has(obj)) return;
visitedNodes.add(obj);
Object.entries(obj).forEach(([key, value]) => {
try {
if (typeof value === "function" && !value[PATCH_SYMBOL]) {
const patchedFunction = new Proxy(value, {
apply: function(target, thisArg, argumentsList) {
console.log(`Promise function called with args:`, argumentsList);
const result = target.apply(thisArg, argumentsList);
if (isPromise(result)) {
result.then(
(res) => console.log(`Promise resolved:`, res),
(err) => console.log(`Promise rejected:`, err)
);
}
return result;
}
});
patchedFunction[PATCH_SYMBOL] = true;
Object.defineProperty(patchedFunction, 'name', { value: `${value.name || 'anonymous'}_patched` });
obj[key] = patchedFunction;
} else if (isObjectOrArray(value) && !isStyleObject(value)) {
logPromiseFunctions(value);
}
} catch (error) {
console.warn(`Error processing function:`, error);
}
});
}
function traverseFiberTree(startNode) {
const stack = [startNode];
while (stack.length > 0) {
try {
const node = stack.pop();
if (!node || typeof node !== "object" || visitedNodes.has(node)) continue;
visitedNodes.add(node);
// Log functions in memoizedProps
if (node.memoizedProps && typeof node.memoizedProps === "object") {
logPromiseFunctions(node.memoizedProps);
}
// Add child and sibling to the stack
if (node.child) stack.push(node.child);
if (node.sibling) stack.push(node.sibling);
} catch (error) {
console.warn("Error in traverseFiberTree:", error);
}
}
}
// Start the traversal
console.log("Starting marked-patch Promise function logging in fiber tree:");
try {
traverseFiberTree(fiberNode);
console.log("Finished setting up Promise function logging. Check console for function calls and Promise resolutions.");
} catch (error) {
console.error("Fatal error in fiber tree traversal:", error);
}
console.log("You can identify patched functions by checking for the presence of the WS_PATCHED symbol.");
console.log("Example usage: if (someFunction[Symbol.for('WS_PATCHED')]) console.log('This function is patched');");
// Get the root fiber node
const main = document.querySelector("#__next");
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
const fiberNode = main[fiberKey];
const collectedGold = [];
const visitedNodes = new WeakSet();
function isObjectOrArray(value) {
return (typeof value === "object" && value !== null) || Array.isArray(value);
}
function isStyleObject(obj) {
// Check if the object has typical style properties
return Object.keys(obj).some((key) =>
["color", "fontSize", "margin", "padding", "display", "position"].includes(
key
)
);
}
function getElementInfo(node) {
if (node.stateNode && node.stateNode.tagName) {
return `<${node.stateNode.tagName.toLowerCase()}>`;
}
if (node.type && typeof node.type === "function") {
return node.type.name || "Anonymous Component";
}
return "Unknown Element";
}
function traverseFiberTree(node, path = "", depth = 0) {
if (
!node ||
typeof node !== "object" ||
depth > 1000 ||
visitedNodes.has(node)
)
return;
visitedNodes.add(node);
// Check memoizedProps
if (node.memoizedProps && typeof node.memoizedProps === "object") {
Object.entries(node.memoizedProps).forEach(([key, value]) => {
if (
key !== "children" &&
isObjectOrArray(value) &&
!isStyleObject(value)
) {
const newPath = `${path}.memoizedProps.${key}`;
collectedGold.push({
path: newPath,
value,
node,
});
}
});
}
// Traverse child
if (node.child) {
traverseFiberTree(node.child, `${path}.child`, depth + 1);
}
// Traverse sibling
if (node.sibling) {
traverseFiberTree(node.sibling, `${path}.sibling`, depth);
}
}
// Start the traversal
console.log("Starting enhanced fiber tree traversal:");
traverseFiberTree(fiberNode);
// Log the collected "gold"
console.log(
"Collected valuable properties (objects and arrays, excluding styles):"
);
collectedGold.forEach(({ path, value, node }) => {
console.log("Value:", value);
});
console.log(`Total valuable properties found: ${collectedGold.length}`);
// Get the root fiber node
const main = document.querySelector("#__next");
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
const fiberNode = main[fiberKey];
const collectedGold = [];
const visitedNodes = new WeakSet();
function isObjectOrArray(value) {
return (typeof value === "object" && value !== null) || Array.isArray(value);
}
function isStyleObject(obj) {
// Check if the object has typical style properties
return Object.keys(obj).some((key) =>
["color", "fontSize", "margin", "padding", "display", "position"].includes(
key
)
);
}
function getElementInfo(node) {
if (node.stateNode && node.stateNode.tagName) {
return `<${node.stateNode.tagName.toLowerCase()}>`;
}
if (node.type && typeof node.type === "function") {
return node.type.name || "Anonymous Component";
}
return "Unknown Element";
}
function traverseFiberTree(node, path = "", depth = 0) {
if (
!node ||
typeof node !== "object" ||
depth > 1000 ||
visitedNodes.has(node)
)
return;
visitedNodes.add(node);
// Check memoizedProps
if (node.memoizedProps && typeof node.memoizedProps === "object") {
Object.entries(node.memoizedProps).forEach(([key, value]) => {
if (
key !== "children" &&
isObjectOrArray(value) &&
!isStyleObject(value)
) {
const newPath = `${path}.memoizedProps.${key}`;
collectedGold.push({
path: newPath,
value,
node,
});
}
});
}
// Traverse child
if (node.child) {
traverseFiberTree(node.child, `${path}.child`, depth + 1);
}
// Traverse sibling
if (node.sibling) {
traverseFiberTree(node.sibling, `${path}.sibling`, depth);
}
}
// Start the traversal
console.log("Starting enhanced fiber tree traversal:");
traverseFiberTree(fiberNode);
// Log the collected "gold"
console.log(
"Collected valuable properties (objects and arrays, excluding styles):"
);
collectedGold.forEach(({ path, value, node }) => {
console.log("Value:", value);
});
console.log(`Total valuable properties found: ${collectedGold.length}`);
// Structured search criteria
const structuredSearchCriteria = [
{
label: "React Query",
criteria: [
{
client: {
queryCache: true,
},
},
],
},
];
// Flatten the criteria for use in the searchReactFiber function
const flattenedCriteria = structuredSearchCriteria.flatMap(
(group) => group.criteria
);
// Search configuration
const searchConfig = {
searchCriteria: flattenedCriteria,
maxDepth: 50,
stopAfterFirst: true,
searchPaths: ["memoizedProps", "memoizedState"],
mainSelector: "body",
callback: (matchingObjects) => {
matchingObjects.forEach(({ matchingObject, fiberNode, criteriaIndex }) => {
const matchedCriteria = flattenedCriteria[criteriaIndex];
const matchedGroup = structuredSearchCriteria.find((group) =>
group.criteria.some((c) => c === matchedCriteria)
);
console.log(`Found ${matchedGroup.label} pattern:`, matchingObject);
if (matchingObject.client.queryCache) {
const queryCache = matchingObject.client.queryCache;
console.log("QueryCache found: ", queryCache);
// objectentries to array of key value pairs
queryCache.queries?.forEach((query) => {
query.state.data && console.log("Query: ", query.state.data);
});
}
// Force rerender the component to see the monkey patching in action
forceRerender(fiberNode);
});
},
};
// Create a WeakSet to keep track of patched objects
const patchedObjects = new WeakSet();
// Safe monkey patching function
function monkeyPatch(obj, prop, callback) {
// Check if the object has already been patched
if (patchedObjects.has(obj)) {
console.log(
`Object containing ${prop} has already been patched. Skipping.`
);
return;
}
const originalFunc = obj[prop];
// Check if the function has already been patched
if (originalFunc.WS_PATCHED) {
console.log(`Function ${prop} has already been patched. Skipping.`);
return;
}
obj[prop] = function (...args) {
const result = originalFunc.apply(this, args);
// Log arguments
console.log(`${prop} called with args:`, args);
// Handle promises
if (result && typeof result.then === "function") {
result.then(
(value) => {
console.log(`${prop} resolved with:`, value);
callback(prop, args, value);
},
(error) => {
console.log(`${prop} rejected with:`, error);
callback(prop, args, null, error);
}
);
} else {
console.log(`${prop} returned:`, result);
callback(prop, args, result);
}
return result;
};
obj[prop].WS_PATCHED = true;
// Mark the object as patched
patchedObjects.add(obj);
}
// Main function to search the React Fiber tree
function searchReactFiber(config) {
const results = [];
const visitedNodes = new WeakSet();
// Helper function to safely access object properties
function safelyAccessProperty(obj, prop) {
try {
return obj[prop];
} catch (error) {
if (error instanceof DOMException && error.name === "SecurityError") {
return null; // Silently skip security-restricted properties
}
throw error;
}
}
// Check if an object contains all keys from the criteria
function isMatchingObject(obj, criteria) {
if (typeof obj !== "object" || obj === null) return false;
return Object.entries(criteria).every(([key, value]) => {
const objValue = safelyAccessProperty(obj, key);
if (objValue === null || objValue === undefined) return false;
if (typeof value === "object" && value !== null) {
return isMatchingObject(objValue, value);
} else if (value === true) {
return true;
} else {
return objValue === value;
}
});
}
// Check if an object matches any of the criteria
function matchesAnyCriteria(obj, criteriaArray) {
for (let i = 0; i < criteriaArray.length; i++) {
if (isMatchingObject(obj, criteriaArray[i])) {
return { matched: true, index: i };
}
}
return { matched: false, index: -1 };
}
// Traverse the Fiber tree
function traverseFiberTree(startNode) {
const stack = [{ node: startNode, depth: 0 }];
while (stack.length > 0) {
const { node, depth } = stack.pop();
if (
!node ||
typeof node !== "object" ||
depth > config.maxDepth ||
visitedNodes.has(node)
) {
continue;
}
visitedNodes.add(node);
// Check if the node or its stateNode has already been patched
if (
patchedObjects.has(node) ||
(node.stateNode && patchedObjects.has(node.stateNode))
) {
console.log("Skipping already patched node or its stateNode");
continue;
}
// Check searchPaths for matching objects
for (const propName of config.searchPaths) {
const propValue = safelyAccessProperty(node, propName);
if (propValue && typeof propValue === "object") {
const match = matchesAnyCriteria(propValue, config.searchCriteria);
if (match.matched) {
results.push({
matchingObject: propValue,
fiberNode: node,
criteriaIndex: match.index,
});
if (config.stopAfterFirst) return results;
}
// Search nested objects in memoizedProps and memoizedState
if (propName === "memoizedProps" || propName === "memoizedState") {
searchNestedObjects(propValue, node);
}
}
}
// Add child and sibling nodes to the stack
const child = safelyAccessProperty(node, "child");
if (child) stack.push({ node: child, depth: depth + 1 });
const sibling = safelyAccessProperty(node, "sibling");
if (sibling) stack.push({ node: sibling, depth });
}
}
// Search nested objects within a node
function searchNestedObjects(obj, fiberNode) {
const stack = [obj];
const visited = new WeakSet();
while (stack.length > 0) {
const current = stack.pop();
if (
typeof current !== "object" ||
current === null ||
visited.has(current)
) {
continue;
}
visited.add(current);
const match = matchesAnyCriteria(current, config.searchCriteria);
if (match.matched) {
results.push({
matchingObject: current,
fiberNode,
criteriaIndex: match.index,
});
if (config.stopAfterFirst) return;
}
// Push all nested objects onto the stack
Object.values(current).forEach((value) => {
if (
typeof value === "object" &&
value !== null &&
!visited.has(value)
) {
stack.push(value);
}
});
}
}
// Get the root fiber node
const main = document.querySelector(config.mainSelector);
if (!main) {
console.warn(
`Main element not found with selector: ${config.mainSelector}`
);
return results;
}
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
if (!fiberKey) {
console.warn(
"React fiber key not found. This may not be a React application or the fiber structure has changed."
);
return results;
}
const fiberNode = safelyAccessProperty(main, fiberKey);
if (!fiberNode) {
console.warn("Unable to access fiber node. Skipping search.");
return results;
}
// Start the search
traverseFiberTree(fiberNode);
// Call the callback function if provided
if (typeof config.callback === "function") {
config.callback(results);
}
return results;
}
// Helper function to force rerender a component
function forceRerender(fiber) {
while (fiber && !fiber.stateNode?.forceUpdate) {
fiber = fiber.return;
}
if (fiber && fiber.stateNode) {
fiber.stateNode.forceUpdate();
}
}
// Execute the search
console.time("Search time");
const matchingObjects = searchReactFiber(searchConfig);
console.timeEnd("Search time");
console.log(`Found ${matchingObjects.length} matching objects`);
// Apply monkey patching to found objects
console.log("Applying monkey patching to found objects...");
matchingObjects.forEach(({ matchingObject, fiberNode, criteriaIndex }) => {
const matchedCriteria = flattenedCriteria[criteriaIndex];
// Check if the object or its associated fiber node has already been patched
if (
patchedObjects.has(matchingObject) ||
patchedObjects.has(fiberNode) ||
(fiberNode.stateNode && patchedObjects.has(fiberNode.stateNode))
) {
console.log("Skipping already patched object or its associated fiber node");
return;
}
Object.keys(matchedCriteria).forEach((key) => {
if (
typeof matchingObject[key] === "function" &&
!matchingObject[key].WS_PATCHED
) {
monkeyPatch(matchingObject, key, (funcName, args, result, error) => {
console.log(`Monkey patched function ${funcName} called:`, {
args,
result,
error,
});
});
}
});
});
console.log(
"Monkey patching completed. State management functions are now being monitored."
);
// Structured search criteria
const structuredSearchCriteria = [
{
label: "React Query",
criteria: [
{
client: {
queryCache: true,
},
},
],
},
];
// Flatten the criteria for use in the searchReactFiber function
const flattenedCriteria = structuredSearchCriteria.flatMap(
(group) => group.criteria
);
// Search configuration
const searchConfig = {
searchCriteria: flattenedCriteria,
maxDepth: 50,
stopAfterFirst: true,
searchPaths: ["memoizedProps", "memoizedState"],
mainSelector: "body",
callback: (matchingObjects) => {
matchingObjects.forEach(({ matchingObject, fiberNode, criteriaIndex }) => {
const matchedCriteria = flattenedCriteria[criteriaIndex];
const matchedGroup = structuredSearchCriteria.find((group) =>
group.criteria.some((c) => c === matchedCriteria)
);
console.log(`Found ${matchedGroup.label} pattern:`, matchingObject);
if (matchingObject.client.queryCache) {
const queryCache = matchingObject.client.queryCache;
console.log("QueryCache found: ", queryCache);
// objectentries to array of key value pairs
queryCache.queries?.forEach((query) => {
query.state.data && console.log("Query: ", query.state.data);
});
}
// Force rerender the component to see the monkey patching in action
forceRerender(fiberNode);
});
},
};
// Create a WeakSet to keep track of patched objects
const patchedObjects = new WeakSet();
// Safe monkey patching function
function monkeyPatch(obj, prop, callback) {
// Check if the object has already been patched
if (patchedObjects.has(obj)) {
console.log(
`Object containing ${prop} has already been patched. Skipping.`
);
return;
}
const originalFunc = obj[prop];
// Check if the function has already been patched
if (originalFunc.WS_PATCHED) {
console.log(`Function ${prop} has already been patched. Skipping.`);
return;
}
obj[prop] = function (...args) {
const result = originalFunc.apply(this, args);
// Log arguments
console.log(`${prop} called with args:`, args);
// Handle promises
if (result && typeof result.then === "function") {
result.then(
(value) => {
console.log(`${prop} resolved with:`, value);
callback(prop, args, value);
},
(error) => {
console.log(`${prop} rejected with:`, error);
callback(prop, args, null, error);
}
);
} else {
console.log(`${prop} returned:`, result);
callback(prop, args, result);
}
return result;
};
obj[prop].WS_PATCHED = true;
// Mark the object as patched
patchedObjects.add(obj);
}
// Main function to search the React Fiber tree
function searchReactFiber(config) {
const results = [];
const visitedNodes = new WeakSet();
// Helper function to safely access object properties
function safelyAccessProperty(obj, prop) {
try {
return obj[prop];
} catch (error) {
if (error instanceof DOMException && error.name === "SecurityError") {
return null; // Silently skip security-restricted properties
}
throw error;
}
}
// Check if an object contains all keys from the criteria
function isMatchingObject(obj, criteria) {
if (typeof obj !== "object" || obj === null) return false;
return Object.entries(criteria).every(([key, value]) => {
const objValue = safelyAccessProperty(obj, key);
if (objValue === null || objValue === undefined) return false;
if (typeof value === "object" && value !== null) {
return isMatchingObject(objValue, value);
} else if (value === true) {
return true;
} else {
return objValue === value;
}
});
}
// Check if an object matches any of the criteria
function matchesAnyCriteria(obj, criteriaArray) {
for (let i = 0; i < criteriaArray.length; i++) {
if (isMatchingObject(obj, criteriaArray[i])) {
return { matched: true, index: i };
}
}
return { matched: false, index: -1 };
}
// Traverse the Fiber tree
function traverseFiberTree(startNode) {
const stack = [{ node: startNode, depth: 0 }];
while (stack.length > 0) {
const { node, depth } = stack.pop();
if (
!node ||
typeof node !== "object" ||
depth > config.maxDepth ||
visitedNodes.has(node)
) {
continue;
}
visitedNodes.add(node);
// Check if the node or its stateNode has already been patched
if (
patchedObjects.has(node) ||
(node.stateNode && patchedObjects.has(node.stateNode))
) {
console.log("Skipping already patched node or its stateNode");
continue;
}
// Check searchPaths for matching objects
for (const propName of config.searchPaths) {
const propValue = safelyAccessProperty(node, propName);
if (propValue && typeof propValue === "object") {
const match = matchesAnyCriteria(propValue, config.searchCriteria);
if (match.matched) {
results.push({
matchingObject: propValue,
fiberNode: node,
criteriaIndex: match.index,
});
if (config.stopAfterFirst) return results;
}
// Search nested objects in memoizedProps and memoizedState
if (propName === "memoizedProps" || propName === "memoizedState") {
searchNestedObjects(propValue, node);
}
}
}
// Add child and sibling nodes to the stack
const child = safelyAccessProperty(node, "child");
if (child) stack.push({ node: child, depth: depth + 1 });
const sibling = safelyAccessProperty(node, "sibling");
if (sibling) stack.push({ node: sibling, depth });
}
}
// Search nested objects within a node
function searchNestedObjects(obj, fiberNode) {
const stack = [obj];
const visited = new WeakSet();
while (stack.length > 0) {
const current = stack.pop();
if (
typeof current !== "object" ||
current === null ||
visited.has(current)
) {
continue;
}
visited.add(current);
const match = matchesAnyCriteria(current, config.searchCriteria);
if (match.matched) {
results.push({
matchingObject: current,
fiberNode,
criteriaIndex: match.index,
});
if (config.stopAfterFirst) return;
}
// Push all nested objects onto the stack
Object.values(current).forEach((value) => {
if (
typeof value === "object" &&
value !== null &&
!visited.has(value)
) {
stack.push(value);
}
});
}
}
// Get the root fiber node
const main = document.querySelector(config.mainSelector);
if (!main) {
console.warn(
`Main element not found with selector: ${config.mainSelector}`
);
return results;
}
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
if (!fiberKey) {
console.warn(
"React fiber key not found. This may not be a React application or the fiber structure has changed."
);
return results;
}
const fiberNode = safelyAccessProperty(main, fiberKey);
if (!fiberNode) {
console.warn("Unable to access fiber node. Skipping search.");
return results;
}
// Start the search
traverseFiberTree(fiberNode);
// Call the callback function if provided
if (typeof config.callback === "function") {
config.callback(results);
}
return results;
}
// Helper function to force rerender a component
function forceRerender(fiber) {
while (fiber && !fiber.stateNode?.forceUpdate) {
fiber = fiber.return;
}
if (fiber && fiber.stateNode) {
fiber.stateNode.forceUpdate();
}
}
// Execute the search
console.time("Search time");
const matchingObjects = searchReactFiber(searchConfig);
console.timeEnd("Search time");
console.log(`Found ${matchingObjects.length} matching objects`);
// Apply monkey patching to found objects
console.log("Applying monkey patching to found objects...");
matchingObjects.forEach(({ matchingObject, fiberNode, criteriaIndex }) => {
const matchedCriteria = flattenedCriteria[criteriaIndex];
// Check if the object or its associated fiber node has already been patched
if (
patchedObjects.has(matchingObject) ||
patchedObjects.has(fiberNode) ||
(fiberNode.stateNode && patchedObjects.has(fiberNode.stateNode))
) {
console.log("Skipping already patched object or its associated fiber node");
return;
}
Object.keys(matchedCriteria).forEach((key) => {
if (
typeof matchingObject[key] === "function" &&
!matchingObject[key].WS_PATCHED
) {
monkeyPatch(matchingObject, key, (funcName, args, result, error) => {
console.log(`Monkey patched function ${funcName} called:`, {
args,
result,
error,
});
});
}
});
});
console.log(
"Monkey patching completed. State management functions are now being monitored."
);
// Configuration object for the search
const searchConfig = {
searchCriteria: { user: { id: true, email: true } }, // What we're looking for
maxDepth: 50, // How deep to search in the tree
stopAfterFirst: false, // Whether to stop after finding the first match
searchPaths: ["memoizedProps", "memoizedState"], // Where to look in each node
mainSelector: "#__next", // The root element of our React app
callback: (matchingObjects) => {
matchingObjects.forEach(({ matchingObject, fiberNode }) => {
console.log("Found matching object:", matchingObject);
console.log(fiberNode)
// Uncomment the next line to force a rerender on each match
// forceRerender(fiberNode);
});
}
};
// Main function to search the React Fiber tree
function searchReactFiber(config) {
const results = [];
const visitedNodes = new WeakSet();
// Helper function to safely access object properties
function safelyAccessProperty(obj, prop) {
try {
return obj[prop];
} catch (error) {
if (error instanceof DOMException && error.name === "SecurityError") {
return null; // Silently skip security-restricted properties
}
throw error;
}
}
// Check if an object matches our search criteria
function isMatchingObject(obj, criteria) {
if (typeof obj !== "object" || obj === null) return false;
for (const [key, value] of Object.entries(criteria)) {
const objValue = safelyAccessProperty(obj, key);
if (objValue === null) return false;
if (typeof value === "object" && value !== null) {
if (!isMatchingObject(objValue, value)) return false;
} else if (value === true) {
if (objValue === undefined) return false;
} else {
if (objValue !== value) return false;
}
}
return true;
}
// Traverse the Fiber tree
function traverseFiberTree(startNode) {
const stack = [{ node: startNode, depth: 0 }];
while (stack.length > 0) {
const { node, depth } = stack.pop();
if (!node || typeof node !== "object" || depth > config.maxDepth || visitedNodes.has(node)) {
continue;
}
visitedNodes.add(node);
// Check searchPaths for matching objects
for (const propName of config.searchPaths) {
const propValue = safelyAccessProperty(node, propName);
if (propValue && typeof propValue === "object") {
if (isMatchingObject(propValue, config.searchCriteria)) {
results.push({ matchingObject: propValue, fiberNode: node });
if (config.stopAfterFirst) return;
}
// Search nested objects in memoizedProps and memoizedState
if (propName === "memoizedProps" || propName === "memoizedState") {
searchNestedObjects(propValue, node);
}
}
}
// Add child and sibling nodes to the stack
const child = safelyAccessProperty(node, 'child');
if (child) stack.push({ node: child, depth: depth + 1 });
const sibling = safelyAccessProperty(node, 'sibling');
if (sibling) stack.push({ node: sibling, depth });
}
}
// Search nested objects within a node
function searchNestedObjects(obj, fiberNode) {
const stack = [obj];
const visited = new WeakSet();
while (stack.length > 0) {
const current = stack.pop();
if (typeof current !== "object" || current === null || visited.has(current)) {
continue;
}
visited.add(current);
if (isMatchingObject(current, config.searchCriteria)) {
results.push({ matchingObject: current, fiberNode });
if (config.stopAfterFirst) return;
}
// Search keys that are likely to contain relevant data
const keysToSearch = Object.keys(current).filter(key =>
typeof current[key] === "object" &&
current[key] !== null &&
!Array.isArray(current[key]) &&
key !== "$$typeof" &&
!key.startsWith("_")
);
for (const key of keysToSearch) {
const value = safelyAccessProperty(current, key);
if (value !== null) stack.push(value);
}
}
}
// Get the root fiber node
const main = document.querySelector(config.mainSelector);
if (!main) {
console.warn(`Main element not found with selector: ${config.mainSelector}`);
return results;
}
const fiberKey = Object.keys(main).find((key) => key.startsWith("__react"));
if (!fiberKey) {
console.warn("React fiber key not found. This may not be a React application or the fiber structure has changed.");
return results;
}
const fiberNode = safelyAccessProperty(main, fiberKey);
if (!fiberNode) {
console.warn("Unable to access fiber node. Skipping search.");
return results;
}
// Start the search
traverseFiberTree(fiberNode);
// Call the callback function if provided
if (typeof config.callback === 'function') {
config.callback(results);
}
return results;
}
// Helper function to force rerender a component
function forceRerender(fiber) {
while (fiber && !fiber.stateNode?.forceUpdate) {
fiber = fiber.return;
}
if (fiber && fiber.stateNode) {
fiber.stateNode.forceUpdate();
}
}
// Execute the search
console.time("Search time");
const matchingObjects = searchReactFiber(searchConfig);
console.timeEnd("Search time");
console.log(`Found ${matchingObjects.length} matching objects`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment