import LRU from 'lru-cache';
import {
convertInspectedElementBackendToFrontend,
hydrateHelper,
inspectElement as inspectElementAPI,
} from 'react-devtools-shared/src/backendAPI';
import {fillInPath} from 'react-devtools-shared/src/hydration';
import type {LRUCache} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {
InspectElementError,
InspectElementFullData,
InspectElementHydratedPath,
} from 'react-devtools-shared/src/backend/types';
import UserError from 'react-devtools-shared/src/errors/UserError';
import UnknownHookError from 'react-devtools-shared/src/errors/UnknownHookError';
import type {
Element,
InspectedElement as InspectedElementFrontend,
InspectedElementResponseType,
InspectedElementPath,
} from 'react-devtools-shared/src/frontend/types';
const inspectedElementCache: LRUCache<number, InspectedElementFrontend> =
new LRU({
max: 25,
});
type InspectElementReturnType = [
InspectedElementFrontend,
InspectedElementResponseType,
];
export function inspectElement(
bridge: FrontendBridge,
element: Element,
path: InspectedElementPath | null,
rendererID: number,
shouldListenToPauseEvents: boolean = false,
): Promise<InspectElementReturnType> {
const {id} = element;
const forceFullData = !inspectedElementCache.has(id);
return inspectElementAPI(
bridge,
forceFullData,
id,
path,
rendererID,
shouldListenToPauseEvents,
).then((data: any) => {
const {type} = data;
let inspectedElement;
switch (type) {
case 'error': {
const {message, stack, errorType} = ((data: any): InspectElementError);
let error;
if (errorType === 'user') {
error = new UserError(message);
} else if (errorType === 'unknown-hook') {
error = new UnknownHookError(message);
} else {
error = new Error(message);
}
error.stack = stack || error.stack;
throw error;
}
case 'no-change':
inspectedElement = inspectedElementCache.get(id);
if (inspectedElement != null) {
return [inspectedElement, type];
}
throw Error(`Cached data for element "${id}" not found`);
case 'not-found':
inspectedElementCache.del(id);
throw Error(`Element "${id}" not found`);
case 'full-data':
const fullData = ((data: any): InspectElementFullData);
inspectedElement = convertInspectedElementBackendToFrontend(
fullData.value,
);
inspectedElementCache.set(id, inspectedElement);
return [inspectedElement, type];
case 'hydrated-path':
const hydratedPathData = ((data: any): InspectElementHydratedPath);
const {value} = hydratedPathData;
inspectedElement = inspectedElementCache.get(id) || null;
if (inspectedElement !== null) {
inspectedElement = {...inspectedElement};
if (path != null) {
fillInPath(
inspectedElement,
value,
path,
hydrateHelper(value, path),
);
}
inspectedElementCache.set(id, inspectedElement);
return [inspectedElement, type];
}
break;
default:
if (__DEV__) {
console.error(
`Unexpected inspected element response data: "${type}"`,
);
}
break;
}
throw Error(`Unable to inspect element with id "${id}"`);
});
}
export function clearCacheForTests(): void {
inspectedElementCache.reset();
}