Skip to content

Commit 5641074

Browse files
committed
added overrideReducer function to intercept useReducer hooks
1 parent 6f28fb4 commit 5641074

File tree

6 files changed

+288
-4
lines changed

6 files changed

+288
-4
lines changed

src/app/components/StateRoute/ComponentMap/ToolTipDataDisplay.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const ToolTipDataDisplay = ({ containerName, dataObj }) => {
3737
return reducerStates.reduce((acc, reducer) => {
3838
acc[reducer.hookName || 'Reducer'] = {
3939
state: reducer.state,
40-
lastAction: reducer.lastAction,
4140
};
4241
return acc;
4342
}, {});

src/backend/types/backendTypes.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,23 @@ export type Fiber = {
252252
export type FiberRoot = {
253253
current: Fiber;
254254
};
255+
256+
export interface Window {
257+
__REACTIME_TIME_TRAVEL__: {
258+
getReducerState: (reducerId: symbol) => any;
259+
travelToState: (reducerId: symbol, targetState: any) => void;
260+
};
261+
__REACTIME_REDUCER_MAP__: Map<
262+
symbol,
263+
{
264+
actionHistory: any[];
265+
dispatch: (action: any) => void;
266+
initialState: any;
267+
currentState: any;
268+
reducer: (state: any, action: any) => any;
269+
}
270+
>;
271+
__REACTIME_DEBUG__?: {
272+
checkOverride: () => void;
273+
};
274+
}

src/extension/build/devtools.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7-
<title>Reactime v23</title>
7+
<title>Reactime v26</title>
88
</head>
99

1010
<body>

src/extension/build/manifest.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Reactime",
3-
"version": "25.0.0",
3+
"version": "26.0.0",
44
"devtools_page": "devtools.html",
55
"description": "A Chrome extension that helps debug React applications by memorizing the state of components with every render.",
66
"manifest_version": 3,
@@ -13,14 +13,21 @@
1313
"128": "assets/whiteBlackSquareIcon128.png"
1414
},
1515
"content_scripts": [
16+
{
17+
"matches": ["<all_urls>"],
18+
"js": ["bundles/overrideReducer.bundle.js"],
19+
"run_at": "document_start",
20+
"all_frames": true,
21+
"world": "MAIN"
22+
},
1623
{
1724
"matches": ["http://localhost/*"],
1825
"js": ["bundles/content.bundle.js"]
1926
}
2027
],
2128
"web_accessible_resources": [
2229
{
23-
"resources": ["bundles/backend.bundle.js"],
30+
"resources": ["bundles/backend.bundle.js", "bundles/overrideReducer.bundle.js"],
2431
"matches": ["<all_urls>"]
2532
}
2633
],

src/extension/overrideReducer.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// (function () {
2+
// console.log('[Reactime Debug] Initial override script loaded');
3+
4+
// let attempts = 0;
5+
// const MAX_ATTEMPTS = 50;
6+
7+
// function verifyReactHooks() {
8+
// return (
9+
// window.React &&
10+
// typeof window.React.useReducer === 'function' &&
11+
// typeof window.React.useState === 'function'
12+
// );
13+
// }
14+
15+
// const checkReact = () => {
16+
// attempts++;
17+
// console.log(`[Reactime Debug] Checking for React (attempt ${attempts})`);
18+
19+
// // Only proceed if we can verify React hooks exist
20+
// if (verifyReactHooks()) {
21+
// console.log('[Reactime Debug] Found React with hooks via window.React');
22+
// setupOverride();
23+
// return;
24+
// }
25+
26+
// // Look for React devtools hook
27+
// if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
28+
// console.log('[Reactime Debug] Found React DevTools hook');
29+
30+
// // Watch for React registration
31+
// const originalInject = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
32+
// console.log('[Reactime Debug] Original inject method:', originalInject); //ellie
33+
34+
// window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function (reactInstance) {
35+
// console.log(
36+
// '[Reactime Debug] React registered with DevTools, verifying hooks availability',
37+
// );
38+
39+
// // Give React a moment to fully initialize
40+
// setTimeout(() => {
41+
// if (verifyReactHooks()) {
42+
// console.log('[Reactime Debug] Hooks verified after DevTools registration');
43+
// setupOverride();
44+
// } else {
45+
// console.log(
46+
// '[Reactime Debug] Hooks not available after DevTools registration, continuing to check',
47+
// );
48+
// waitForHooks();
49+
// }
50+
// }, 2000);
51+
52+
// return originalInject.apply(this, arguments);
53+
// };
54+
55+
// return;
56+
// }
57+
58+
// waitForHooks();
59+
// };
60+
61+
// function waitForHooks() {
62+
// if (attempts < MAX_ATTEMPTS) {
63+
// const delay = Math.min(100 * attempts, 2000);
64+
// console.log(`[Reactime Debug] Hooks not found, retrying in ${delay}ms`);
65+
// setTimeout(checkReact, delay);
66+
// } else {
67+
// console.log('[Reactime Debug] Max attempts reached, React hooks not found');
68+
// }
69+
// }
70+
71+
// function setupOverride() {
72+
// try {
73+
// console.log('[Reactime Debug] Setting up useReducer override');
74+
75+
// if (!verifyReactHooks()) {
76+
// throw new Error('React hooks not available during override setup');
77+
// }
78+
79+
// const originalUseReducer = window.React.useReducer;
80+
// window.__REACTIME_REDUCER_MAP__ = new Map();
81+
82+
// window.React.useReducer = function (reducer, initialArg, init) {
83+
// console.log('[Reactime Debug] useReducer called with:', {
84+
// reducerName: reducer?.name || 'anonymous',
85+
// hasInitialArg: initialArg !== undefined,
86+
// hasInit: !!init,
87+
// });
88+
89+
// const actualInitialState = init ? init(initialArg) : initialArg;
90+
91+
// const wrappedReducer = (state, action) => {
92+
// try {
93+
// console.log('[Reactime Debug] Reducer called:', {
94+
// actionType: action?.type,
95+
// isTimeTravel: action?.type === '__REACTIME_TIME_TRAVEL__',
96+
// currentState: state,
97+
// action,
98+
// });
99+
100+
// if (action && action.type === '__REACTIME_TIME_TRAVEL__') {
101+
// return action.payload;
102+
// }
103+
// return reducer(state, action);
104+
// } catch (error) {
105+
// console.error('[Reactime Debug] Error in wrapped reducer:', error);
106+
// return state;
107+
// }
108+
// };
109+
110+
// const [state, dispatch] = originalUseReducer(wrappedReducer, actualInitialState);
111+
// const reducerId = Symbol('reactimeReducer');
112+
113+
// console.log('[Reactime Debug] New reducer instance created:', {
114+
// reducerId: reducerId.toString(),
115+
// initialState: actualInitialState,
116+
// currentState: state,
117+
// });
118+
119+
// window.__REACTIME_REDUCER_MAP__.set(reducerId, {
120+
// actionHistory: [],
121+
// dispatch,
122+
// initialState: actualInitialState,
123+
// currentState: state,
124+
// reducer: wrappedReducer,
125+
// });
126+
127+
// const wrappedDispatch = (action) => {
128+
// try {
129+
// console.log('[Reactime Debug] Dispatch called:', {
130+
// reducerId: reducerId.toString(),
131+
// action,
132+
// currentMapSize: window.__REACTIME_REDUCER_MAP__.size,
133+
// });
134+
135+
// const reducerInfo = window.__REACTIME_REDUCER_MAP__.get(reducerId);
136+
// reducerInfo.actionHistory.push(action);
137+
// reducerInfo.currentState = wrappedReducer(reducerInfo.currentState, action);
138+
// dispatch(action);
139+
// } catch (error) {
140+
// console.error('[Reactime Debug] Error in wrapped dispatch:', error);
141+
// dispatch(action);
142+
// }
143+
// };
144+
145+
// return [state, wrappedDispatch];
146+
// };
147+
148+
// console.log('[Reactime Debug] useReducer successfully overridden');
149+
// } catch (error) {
150+
// console.error('[Reactime Debug] Error during override setup:', error);
151+
// // If override fails, try again after a delay
152+
// setTimeout(checkReact, 500);
153+
// }
154+
// }
155+
156+
// // Start checking for React
157+
// checkReact();
158+
159+
// // Watch for dynamic React loading
160+
// const observer = new MutationObserver((mutations) => {
161+
// if (verifyReactHooks()) {
162+
// console.log('[Reactime Debug] React hooks found after DOM mutation');
163+
// observer.disconnect();
164+
// setupOverride();
165+
// }
166+
// });
167+
168+
// observer.observe(document, {
169+
// childList: true,
170+
// subtree: true,
171+
// });
172+
// })();
173+
174+
175+
(function () {
176+
console.log('[Reactime Debug] Initial override script loaded');
177+
178+
// Retry constants
179+
const MAX_ATTEMPTS = 50;
180+
let attempts = 0;
181+
182+
// Verify React hooks via registered renderers
183+
function verifyReactHooks() {
184+
try {
185+
const renderers = Array.from(
186+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values()
187+
);
188+
return renderers.some((renderer) => renderer?.currentDispatcher?.useReducer);
189+
} catch (err) {
190+
console.error('[Reactime Debug] Error verifying React hooks:', err);
191+
return false;
192+
}
193+
}
194+
195+
// Set up the useReducer override
196+
function setupOverride() {
197+
console.log('[Reactime Debug] Setting up useReducer override');
198+
199+
try {
200+
const renderers = Array.from(
201+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values()
202+
);
203+
const renderer = renderers[0]; // Assume first renderer for simplicity
204+
const originalUseReducer = renderer?.currentDispatcher?.useReducer;
205+
206+
if (!originalUseReducer) {
207+
throw new Error('useReducer not found in React renderer.');
208+
}
209+
210+
renderer.currentDispatcher.useReducer = function (reducer, initialArg, init) {
211+
console.log('[Reactime Debug] useReducer intercepted:', reducer.name || 'anonymous');
212+
213+
const wrappedReducer = (state, action) => {
214+
console.log('[Reactime Debug] Reducer called:', { state, action });
215+
return reducer(state, action);
216+
};
217+
218+
return originalUseReducer(wrappedReducer, initialArg, init);
219+
};
220+
221+
console.log('[Reactime Debug] useReducer successfully overridden.');
222+
} catch (err) {
223+
console.error('[Reactime Debug] Error in setupOverride:', err);
224+
}
225+
}
226+
227+
// Attempt to detect React and set up override
228+
function checkReact() {
229+
attempts++;
230+
console.log(`[Reactime Debug] Checking for React (attempt ${attempts}/${MAX_ATTEMPTS})`);
231+
232+
if (verifyReactHooks()) {
233+
console.log('[Reactime Debug] React hooks found. Setting up overrides.');
234+
setupOverride();
235+
} else if (attempts < MAX_ATTEMPTS) {
236+
setTimeout(checkReact, Math.min(100 * attempts, 2000));
237+
} else {
238+
console.log('[Reactime Debug] Max attempts reached. React hooks not found.');
239+
}
240+
}
241+
242+
// Hook into the inject method of React DevTools
243+
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
244+
const originalInject = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
245+
246+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function (renderer) {
247+
console.log('[Reactime Debug] React renderer registered.');
248+
setupOverride(); // React is registered, so immediately set up overrides
249+
return originalInject.apply(this, arguments);
250+
};
251+
252+
console.log('[Reactime Debug] React DevTools hook overridden.');
253+
} else {
254+
console.log('[Reactime Debug] React DevTools hook not found. Starting manual checks.');
255+
checkReact(); // Start retries if no DevTools hook
256+
}
257+
})();

webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
background: './src/extension/background.js',
1818
content: './src/extension/contentScript.ts',
1919
backend: './src/backend/index.ts',
20+
overrideReducer: './src/extension/overrideReducer.js',
2021
},
2122
watchOptions: {
2223
aggregateTimeout: 1000,

0 commit comments

Comments
 (0)