mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
fix: retrieval of state
This commit is contained in:
+404
-26
@@ -10,6 +10,20 @@ class ReactFiber {
|
||||
console.log("Selected Nodes:", this.nodes);
|
||||
console.log("🔍 Found Fibers:", this.fibers);
|
||||
console.log("🛠 Found Components:", this.components);
|
||||
|
||||
// Debug fiber info
|
||||
this.fibers.forEach((fiber, index) => {
|
||||
if (fiber) {
|
||||
console.log(`Fiber ${index}:`, {
|
||||
tag: fiber.tag,
|
||||
type: fiber.type?.name || fiber.type,
|
||||
elementType: fiber.elementType,
|
||||
stateNode: fiber.stateNode,
|
||||
hasState: !!fiber.stateNode?.state,
|
||||
hasMemoizedState: !!fiber.memoizedState
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +33,27 @@ class ReactFiber {
|
||||
|
||||
getFiberNode(node) {
|
||||
if (!node) return null;
|
||||
|
||||
// Try multiple property name patterns for different React versions
|
||||
const possibleKeys = [
|
||||
'__reactFiber$', // React 16+
|
||||
'__reactInternalFiber$', // React 15
|
||||
'__reactInternalInstance$', // Older versions
|
||||
'__reactFiber',
|
||||
'__reactInternalInstance'
|
||||
];
|
||||
|
||||
// Check for exact matches first
|
||||
for (const key of possibleKeys) {
|
||||
if (node[key]) return node[key];
|
||||
}
|
||||
|
||||
// Fall back to pattern matching
|
||||
const fiberKey = Object.getOwnPropertyNames(node).find(
|
||||
(name) =>
|
||||
name.startsWith("__reactFiber") ||
|
||||
name.startsWith("__reactInternalInstance"),
|
||||
name.startsWith("__reactInternalInstance") ||
|
||||
name.startsWith("__reactInternalFiber")
|
||||
);
|
||||
return fiberKey ? node[fiberKey] : null;
|
||||
}
|
||||
@@ -30,20 +61,71 @@ class ReactFiber {
|
||||
getOwnerComponent(fiberNode) {
|
||||
let current = fiberNode;
|
||||
while (current) {
|
||||
// Use React's internal tag system to identify component types
|
||||
// Based on React's WorkTags: ClassComponent = 1, FunctionComponent = 0
|
||||
if (current.tag === 1) { // ClassComponent
|
||||
return current.stateNode; // For class components, stateNode is the component instance
|
||||
}
|
||||
|
||||
// For function components, look for hooks in memoizedState
|
||||
if (current.tag === 0 || current.tag === 15) { // FunctionComponent or MemoComponent
|
||||
// Function components don't have setState, but we can still track them
|
||||
if (current.memoizedState && current.type) {
|
||||
return {
|
||||
type: 'function',
|
||||
hooks: current.memoizedState,
|
||||
fiber: current,
|
||||
forceUpdate: () => {
|
||||
// Trigger re-render by updating fiber
|
||||
if (current.alternate) {
|
||||
current.alternate.expirationTime = 1;
|
||||
}
|
||||
current.expirationTime = 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy fallback: check if stateNode has React component methods
|
||||
if (
|
||||
current.stateNode &&
|
||||
current.stateNode !== null &&
|
||||
typeof current.stateNode === 'object' &&
|
||||
(current.stateNode.setState || current.stateNode.forceUpdate)
|
||||
) {
|
||||
return current.stateNode;
|
||||
}
|
||||
|
||||
current = current.return;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getState(key) {
|
||||
if (!this.components.length) return null;
|
||||
const state = this.components[0]?.state || null;
|
||||
if (!this.components.length && !this.fibers.length) return null;
|
||||
|
||||
const component = this.components[0];
|
||||
const fiber = this.fibers[0];
|
||||
let state = null;
|
||||
|
||||
// Handle class components
|
||||
if (component?.state) {
|
||||
state = component.state;
|
||||
}
|
||||
// Handle function components with hooks - look directly at fiber
|
||||
else if (fiber?.memoizedState) {
|
||||
if (this.debug) {
|
||||
console.log("🔍 Raw fiber.memoizedState:", fiber.memoizedState);
|
||||
}
|
||||
// Extract useState values from the hook chain
|
||||
const states = this.extractStateFromHooks(fiber.memoizedState);
|
||||
state = states.length === 1 ? states[0] : states;
|
||||
}
|
||||
// Fallback: try component hooks if available
|
||||
else if (component?.type === 'function' && component?.hooks) {
|
||||
const states = this.extractStateFromHooks(component.hooks);
|
||||
state = states.length === 1 ? states[0] : states;
|
||||
}
|
||||
|
||||
if (key === undefined) {
|
||||
return state;
|
||||
@@ -61,8 +143,137 @@ class ReactFiber {
|
||||
return null;
|
||||
}
|
||||
|
||||
extractStateFromHooks(hookChain) {
|
||||
const states = [];
|
||||
let mainStateFound = false;
|
||||
let currentHook = hookChain;
|
||||
let hookIndex = 0;
|
||||
|
||||
if (this.debug) {
|
||||
console.log("🔍 Hook chain analysis:");
|
||||
}
|
||||
|
||||
while (currentHook) {
|
||||
if (this.debug) {
|
||||
console.log(`Hook ${hookIndex}:`, {
|
||||
type: currentHook.tag || 'unknown',
|
||||
memoizedState: currentHook.memoizedState,
|
||||
queue: currentHook.queue,
|
||||
next: !!currentHook.next
|
||||
});
|
||||
}
|
||||
|
||||
// Try different approaches to extract state
|
||||
if (currentHook.memoizedState !== undefined && currentHook.memoizedState !== null) {
|
||||
const state = currentHook.memoizedState;
|
||||
|
||||
// Priority 1: Check for useRef hooks with complex state in .current
|
||||
if (!currentHook.queue &&
|
||||
typeof state === 'object' &&
|
||||
state !== null &&
|
||||
state.current !== undefined &&
|
||||
typeof state.current === 'object' &&
|
||||
state.current !== null) {
|
||||
|
||||
// Check if this looks like a substantial state object (has multiple properties)
|
||||
const currentKeys = Object.keys(state.current);
|
||||
if (currentKeys.length > 2) {
|
||||
states.push(state.current);
|
||||
mainStateFound = true;
|
||||
if (this.debug) console.log(` 🎯 Found main state in useRef:`, state.current);
|
||||
}
|
||||
}
|
||||
// Priority 2: useState hooks with queue
|
||||
else if (currentHook.queue && typeof state !== 'function') {
|
||||
states.push(state);
|
||||
if (this.debug) console.log(` ✅ Found useState state:`, state);
|
||||
}
|
||||
// Priority 3: Other potential state objects (only if we haven't found main state)
|
||||
else if (!mainStateFound && !currentHook.queue && typeof state === 'object' && state !== null) {
|
||||
// Skip useEffect hooks (they have tag 36)
|
||||
if (!(state.tag === 36 && state.create)) {
|
||||
states.push(state);
|
||||
if (this.debug) console.log(` 📦 Found potential state object:`, state);
|
||||
}
|
||||
}
|
||||
// Priority 4: Simple primitive state
|
||||
else if (typeof state !== 'function' && typeof state !== 'object') {
|
||||
states.push(state);
|
||||
if (this.debug) console.log(` 🔹 Found primitive state:`, state);
|
||||
}
|
||||
}
|
||||
|
||||
currentHook = currentHook.next;
|
||||
hookIndex++;
|
||||
}
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`🎯 Extracted ${states.length} state values:`, states);
|
||||
}
|
||||
|
||||
// If we found main state objects, prioritize and deduplicate them
|
||||
if (mainStateFound && states.length > 1) {
|
||||
const mainStates = states.filter(state =>
|
||||
typeof state === 'object' &&
|
||||
state !== null &&
|
||||
Object.keys(state).length > 2
|
||||
);
|
||||
|
||||
if (mainStates.length > 1) {
|
||||
// If we have multiple main state objects, find the most comprehensive one
|
||||
// or merge them if they seem complementary
|
||||
const largestState = mainStates.reduce((largest, current) => {
|
||||
const largestKeys = Object.keys(largest).length;
|
||||
const currentKeys = Object.keys(current).length;
|
||||
|
||||
// Prefer the one with more properties
|
||||
if (currentKeys > largestKeys) return current;
|
||||
|
||||
// If same number of properties, prefer the one with more complex data
|
||||
if (currentKeys === largestKeys) {
|
||||
const largestComplexity = this.calculateStateComplexity(largest);
|
||||
const currentComplexity = this.calculateStateComplexity(current);
|
||||
return currentComplexity > largestComplexity ? current : largest;
|
||||
}
|
||||
|
||||
return largest;
|
||||
});
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`🎯 Selected most comprehensive state from ${mainStates.length} candidates:`, largestState);
|
||||
}
|
||||
|
||||
return [largestState];
|
||||
}
|
||||
|
||||
return mainStates;
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
calculateStateComplexity(state) {
|
||||
if (!state || typeof state !== 'object') return 0;
|
||||
|
||||
let complexity = 0;
|
||||
for (const [key, value] of Object.entries(state)) {
|
||||
complexity += 1; // Base point for each property
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
complexity += value.length * 0.1; // Arrays get points based on length
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
complexity += Object.keys(value).length * 0.5; // Nested objects get points
|
||||
} else if (typeof value === 'function') {
|
||||
complexity += 2; // Functions are valuable
|
||||
}
|
||||
}
|
||||
|
||||
return complexity;
|
||||
}
|
||||
|
||||
setState(update) {
|
||||
this.components.forEach((component) => {
|
||||
// Handle class components
|
||||
if (component?.setState) {
|
||||
if (typeof update === "function") {
|
||||
// Functional update
|
||||
@@ -85,6 +296,13 @@ class ReactFiber {
|
||||
});
|
||||
}
|
||||
}
|
||||
// Handle function components - force re-render since we can't directly update hooks
|
||||
else if (component?.type === 'function' && component?.forceUpdate) {
|
||||
if (this.debug) {
|
||||
console.log("⚠️ Function component detected - triggering re-render. Direct state update not possible.");
|
||||
}
|
||||
component.forceUpdate();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
@@ -99,7 +317,7 @@ class ReactFiber {
|
||||
return this.fibers[0]?.memoizedProps?.[propName];
|
||||
}
|
||||
|
||||
setProp(propName) {
|
||||
setProp(propName, value) {
|
||||
this.fibers.forEach((fiber) => {
|
||||
if (fiber?.memoizedProps) {
|
||||
fiber.memoizedProps[propName] = value;
|
||||
@@ -119,38 +337,176 @@ class ReactFiber {
|
||||
}
|
||||
}
|
||||
|
||||
function makeSerializable(obj) {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
function makeSerializable(obj, visited = new WeakSet(), depth = 0, maxDepth = 10) {
|
||||
// Handle primitives first
|
||||
if (obj === null || obj === undefined) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Catch ALL functions early
|
||||
if (typeof obj === "function") {
|
||||
return `[Function: ${obj.name || 'anonymous'}]`;
|
||||
}
|
||||
|
||||
if (typeof obj !== "object") {
|
||||
// Handle other primitives
|
||||
if (typeof obj === "symbol") return obj.toString();
|
||||
if (typeof obj === "bigint") return obj.toString() + "n";
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => makeSerializable(item));
|
||||
// Prevent infinite recursion - depth limit
|
||||
if (depth > maxDepth) {
|
||||
return "[Max Depth Reached]";
|
||||
}
|
||||
|
||||
const serializableObj = {};
|
||||
for (const key in obj) {
|
||||
if (Object.hasOwn(obj, key)) {
|
||||
let value = obj[key];
|
||||
// Prevent circular references
|
||||
if (visited.has(obj)) {
|
||||
return "[Circular Reference]";
|
||||
}
|
||||
visited.add(obj);
|
||||
|
||||
if (typeof value === "function") {
|
||||
value = "[Function]";
|
||||
} else if (value instanceof HTMLElement) {
|
||||
value = {
|
||||
type: "HTMLElement",
|
||||
id: value.id,
|
||||
tagName: value.tagName,
|
||||
}; // Replace DOM node with ID/tag info
|
||||
} else if (typeof value === "symbol") {
|
||||
value = value.toString();
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
value = makeSerializable(value);
|
||||
try {
|
||||
// Handle special objects first
|
||||
if (obj instanceof HTMLElement) {
|
||||
return {
|
||||
type: "HTMLElement",
|
||||
tagName: obj.tagName,
|
||||
id: obj.id || null,
|
||||
className: obj.className || null,
|
||||
attributes: obj.attributes ? Array.from(obj.attributes).map(attr => ({ name: attr.name, value: attr.value })) : []
|
||||
};
|
||||
}
|
||||
|
||||
if (obj instanceof Event) {
|
||||
return {
|
||||
type: "Event",
|
||||
eventType: obj.type,
|
||||
target: obj.target?.tagName || null
|
||||
};
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return { type: "Date", value: obj.toISOString() };
|
||||
}
|
||||
|
||||
if (obj instanceof RegExp) {
|
||||
return { type: "RegExp", source: obj.source, flags: obj.flags };
|
||||
}
|
||||
|
||||
if (obj instanceof Error) {
|
||||
return { type: "Error", message: obj.message, name: obj.name };
|
||||
}
|
||||
|
||||
// Handle React Fiber nodes - these are super circular
|
||||
if (obj.tag !== undefined && obj.elementType !== undefined) {
|
||||
return {
|
||||
type: "ReactFiber",
|
||||
tag: obj.tag,
|
||||
elementType: typeof obj.elementType === 'function' ? obj.elementType.name || 'AnonymousComponent' : String(obj.elementType),
|
||||
key: obj.key,
|
||||
hasState: !!obj.stateNode?.state,
|
||||
hasMemoizedState: !!obj.memoizedState,
|
||||
hasProps: !!obj.memoizedProps
|
||||
};
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.slice(0, 50).map((item, index) => {
|
||||
if (index >= 25) return "[...truncated]"; // Smaller limit
|
||||
return makeSerializable(item, visited, depth + 1, maxDepth);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle regular objects
|
||||
const serializableObj = {};
|
||||
|
||||
// Get own enumerable properties only to avoid prototype pollution
|
||||
const ownKeys = Object.getOwnPropertyNames(obj).filter(key => {
|
||||
try {
|
||||
return obj.propertyIsEnumerable(key);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Limit number of properties to avoid huge objects
|
||||
const maxKeys = 30; // Smaller limit for safety
|
||||
const processedKeys = ownKeys.slice(0, maxKeys);
|
||||
|
||||
for (const key of processedKeys) {
|
||||
try {
|
||||
// Skip problematic keys early
|
||||
const dangerousKeys = [
|
||||
'parentNode', 'parentElement', 'ownerDocument', 'children', 'childNodes',
|
||||
'return', 'child', 'sibling', 'alternate', 'ref', // React Fiber circular refs
|
||||
'_owner', '_source', '_self', '_debugOwner', '_debugSource', // React internals
|
||||
'window', 'document', 'global', 'self', 'top', 'parent', // Global objects
|
||||
'constructor', 'prototype', '__proto__', // Constructor/prototype chains
|
||||
'addEventListener', 'removeEventListener', // Event handlers
|
||||
'setState', 'forceUpdate', 'render' // React methods that might be functions
|
||||
];
|
||||
|
||||
if (dangerousKeys.includes(key)) {
|
||||
serializableObj[key] = `[Skipped: ${key}]`;
|
||||
continue;
|
||||
}
|
||||
|
||||
serializableObj[key] = value;
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||
if (descriptor && (descriptor.get || descriptor.set)) {
|
||||
serializableObj[key] = "[Getter/Setter]";
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = obj[key];
|
||||
|
||||
// Handle symbols specifically (React context symbols)
|
||||
if (typeof value === "symbol") {
|
||||
value = `[Symbol: ${value.description || 'anonymous'}]`;
|
||||
}
|
||||
// Extra function check
|
||||
else if (typeof value === "function") {
|
||||
value = `[Function: ${value.name || 'anonymous'}]`;
|
||||
} else if (value && typeof value === "object") {
|
||||
value = makeSerializable(value, visited, depth + 1, maxDepth);
|
||||
}
|
||||
|
||||
serializableObj[key] = value;
|
||||
} catch (error) {
|
||||
serializableObj[key] = `[Error: ${error.message}]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (ownKeys.length > maxKeys) {
|
||||
serializableObj['...'] = `[${ownKeys.length - maxKeys} more properties]`;
|
||||
}
|
||||
|
||||
return serializableObj;
|
||||
} catch (error) {
|
||||
return `[Serialization Error: ${error.message}]`;
|
||||
} finally {
|
||||
visited.delete(obj); // Clean up for potential reuse
|
||||
}
|
||||
}
|
||||
|
||||
// Final safety check - recursively scan for any remaining functions
|
||||
function deepFunctionCheck(obj, path = "") {
|
||||
if (typeof obj === "function") {
|
||||
throw new Error(`Found function at path: ${path}`);
|
||||
}
|
||||
|
||||
if (obj && typeof obj === "object") {
|
||||
if (Array.isArray(obj)) {
|
||||
obj.forEach((item, index) => {
|
||||
deepFunctionCheck(item, `${path}[${index}]`);
|
||||
});
|
||||
} else {
|
||||
Object.keys(obj).forEach(key => {
|
||||
deepFunctionCheck(obj[key], path ? `${path}.${key}` : key);
|
||||
});
|
||||
}
|
||||
}
|
||||
return serializableObj;
|
||||
}
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
@@ -196,6 +552,28 @@ window.addEventListener("message", (event) => {
|
||||
response = makeSerializable(response);
|
||||
}
|
||||
|
||||
// Final safety check before postMessage
|
||||
try {
|
||||
deepFunctionCheck(response);
|
||||
} catch (functionError) {
|
||||
console.warn("[pageState] Function detected in response, cleaning:", functionError.message);
|
||||
response = `[Cleaned Response - Function found at: ${functionError.message}]`;
|
||||
}
|
||||
|
||||
// Additional structured clone test
|
||||
try {
|
||||
// Test if the object can be cloned (same algorithm as postMessage)
|
||||
if (typeof structuredClone === 'function') {
|
||||
structuredClone(response);
|
||||
} else {
|
||||
// Fallback for older browsers - try JSON round-trip
|
||||
JSON.parse(JSON.stringify(response));
|
||||
}
|
||||
} catch (cloneError) {
|
||||
console.warn("[pageState] Response not cloneable, fallback:", cloneError.message);
|
||||
response = `[Uncloneable Response: ${cloneError.message}]`;
|
||||
}
|
||||
|
||||
window.postMessage(
|
||||
{
|
||||
type: "reactFiberResponse",
|
||||
|
||||
@@ -17,7 +17,7 @@ const handleNotificationClick = async (target: HTMLElement) => {
|
||||
if (!buttonId) return;
|
||||
|
||||
const matchingNotification =
|
||||
notificationList.storeState.notifications.items.find(
|
||||
notificationList.items.find(
|
||||
(item: any) => item.notificationID === parseInt(buttonId),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user