// IMPORTANT: // this file is super important to load the __REACT_DEVTOOLS_GLOBAL_HOOK__ object // without this, we can't stub the React DevTools global hook, we don't have a way to instrument the application // make sure you import this file first before anything else (particularly React) import type { ReactDevToolsGlobalHook, ReactRenderer } from "./types.js"; export const version = process.env.VERSION; export const BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`; const objectDefineProperty = Object.defineProperty; // eslint-disable-next-line @typescript-eslint/unbound-method const objectHasOwnProperty = Object.prototype.hasOwnProperty; const NO_OP = () => { /**/ }; const checkDCE = (fn: unknown): void => { try { const code = Function.prototype.toString.call(fn); if (code.indexOf("^_^") > -1) { setTimeout(() => { throw new Error( "React is running in production mode, but dead code " + "elimination has not been applied. Read how to correctly " + "configure React for production: " + "https://reactjs.org/link/perf-use-production-build", ); }); } } catch {} }; export const isRealReactDevtools = ( rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__, ): boolean => { return Boolean(rdtHook && "getFiberRoots" in rdtHook); }; let isReactRefreshOverride = false; let injectFnStr: string | undefined = undefined; export const isReactRefresh = ( rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__, ): boolean => { if (isReactRefreshOverride) return true; if (rdtHook && typeof rdtHook.inject === "function") { injectFnStr = rdtHook.inject.toString(); } // https://github.com/facebook/react/blob/8f8b336734d7c807f5aa11b0f31540e63302d789/packages/react-refresh/src/ReactFreshRuntime.js#L459 return Boolean(injectFnStr?.includes("(injected)")); }; const onActiveListeners = new Set<() => unknown>(); export const _renderers = new Set(); export const installRDTHook = (onActive?: () => unknown): ReactDevToolsGlobalHook => { const renderers = new Map(); let i = 0; let rdtHook: ReactDevToolsGlobalHook = { _instrumentationIsActive: false, _instrumentationSource: BIPPY_INSTRUMENTATION_STRING, checkDCE, hasUnsupportedRendererAttached: false, inject(renderer) { const nextID = ++i; renderers.set(nextID, renderer); _renderers.add(renderer); if (!rdtHook._instrumentationIsActive) { rdtHook._instrumentationIsActive = true; onActiveListeners.forEach((listener) => listener()); } return nextID; }, on: NO_OP, onCommitFiberRoot: NO_OP, onCommitFiberUnmount: NO_OP, onPostCommitFiberRoot: NO_OP, renderers, supportsFiber: true, supportsFlight: true, }; try { objectDefineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", { configurable: true, enumerable: true, get() { return rdtHook; }, set(newHook) { if (newHook && typeof newHook === "object") { const ourRenderers = rdtHook.renderers; rdtHook = newHook; if (ourRenderers.size > 0) { ourRenderers.forEach((renderer, id) => { _renderers.add(renderer); // eslint-disable-next-line @typescript-eslint/no-unsafe-call newHook.renderers.set(id, renderer); }); patchRDTHook(onActive); } } }, }); // [!] this is a hack for chrome extensions - if we install before React DevTools, we could accidently prevent React DevTools from installing: // https://github.com/facebook/react/blob/18eaf51bd51fed8dfed661d64c306759101d0bfd/packages/react-devtools-extensions/src/contentScripts/installHook.js#L30C6-L30C27 // eslint-disable-next-line @typescript-eslint/unbound-method const originalWindowHasOwnProperty = window.hasOwnProperty; let hasRanHack = false; objectDefineProperty(window, "hasOwnProperty", { configurable: true, value: function (this: unknown, ...args: [PropertyKey]) { try { if (!hasRanHack && args[0] === "__REACT_DEVTOOLS_GLOBAL_HOOK__") { globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined; // special falsy value to know that we've already installed before hasRanHack = true; return -0; } } catch {} return originalWindowHasOwnProperty.apply(this, args); }, writable: true, }); } catch { patchRDTHook(onActive); } return rdtHook; }; export const patchRDTHook = (onActive?: () => unknown): void => { if (onActive) { onActiveListeners.add(onActive); } try { const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (!rdtHook) return; if (!rdtHook._instrumentationSource) { rdtHook.checkDCE = checkDCE; rdtHook.supportsFiber = true; rdtHook.supportsFlight = true; rdtHook.hasUnsupportedRendererAttached = false; rdtHook._instrumentationSource = BIPPY_INSTRUMENTATION_STRING; rdtHook._instrumentationIsActive = false; // we need to be careful here (needs to be below _instrumentationSource) else it causes excessive recursion const isReactDevtools = isRealReactDevtools(rdtHook); if (!isReactDevtools) { rdtHook.on = NO_OP; } if (rdtHook.renderers.size) { rdtHook._instrumentationIsActive = true; onActiveListeners.forEach((listener) => listener()); return; } const prevInject = rdtHook.inject; const isRefresh = isReactRefresh(rdtHook); if (isRefresh && !isReactDevtools) { isReactRefreshOverride = true; // but since the underlying implementation doens't care, // it's ok: https://github.com/facebook/react/blob/18eaf51bd51fed8dfed661d64c306759101d0bfd/packages/react-refresh/src/ReactFreshRuntime.js#L430 const nextID = rdtHook.inject({ scheduleRefresh() {}, } as unknown as ReactRenderer); if (nextID) { rdtHook._instrumentationIsActive = true; } } rdtHook.inject = (renderer) => { const id = prevInject(renderer); _renderers.add(renderer); if (isRefresh) { // react refresh doesn't inject this properly // https://github.com/facebook/react/blob/18eaf51bd51fed8dfed661d64c306759101d0bfd/packages/react-refresh/src/ReactFreshRuntime.js#L430 rdtHook.renderers.set(id, renderer); } rdtHook._instrumentationIsActive = true; onActiveListeners.forEach((listener) => listener()); return id; }; } if ( rdtHook.renderers.size || rdtHook._instrumentationIsActive || // depending on this to inject is unsafe, since inject could occur before and we wouldn't know isReactRefresh() ) { onActive?.(); } } catch {} }; export const hasRDTHook = (): boolean => { return objectHasOwnProperty.call(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__"); }; /** * Returns the current React DevTools global hook. */ export const getRDTHook = (onActive?: () => unknown): ReactDevToolsGlobalHook => { if (!hasRDTHook()) { return installRDTHook(onActive); } patchRDTHook(onActive); // must exist at this point return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ as ReactDevToolsGlobalHook; }; export const isClientEnvironment = (): boolean => { return Boolean( typeof window !== "undefined" && // eslint-disable-next-line @typescript-eslint/unbound-method (window.document?.createElement || window.navigator?.product === "ReactNative"), ); }; /** * Usually used purely for side effect */ export const safelyInstallRDTHook = () => { try { // __REACT_DEVTOOLS_GLOBAL_HOOK__ must exist before React is ever executed if (isClientEnvironment()) { getRDTHook(); } } catch {} };