From 4a93a91aa447c0abebeb5134a2cefbb7e9fbbdb0 Mon Sep 17 00:00:00 2001 From: Pavel Ivanov Date: Sat, 14 Dec 2024 02:06:53 +0200 Subject: [PATCH] fix: improve proxy and type handling in inspect element - Remove unreliable proxy detection and instanceof checks - Add safer URLSearchParams handling to prevent Next.js errors - Use capability checks instead of type detection - Improve error handling for property access This addresses issues with proxy detection and type checking in the element inspector, making it more reliable across different environments while preventing Next.js-specific errors when handling search params. --- .../core/web/inspect-element/view-state.ts | 133 +++++++++--------- 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/packages/scan/src/core/web/inspect-element/view-state.ts b/packages/scan/src/core/web/inspect-element/view-state.ts index bd7698ca..46843ea3 100644 --- a/packages/scan/src/core/web/inspect-element/view-state.ts +++ b/packages/scan/src/core/web/inspect-element/view-state.ts @@ -12,50 +12,45 @@ const EXPANDED_PATHS = new Set(); const fadeOutTimers = new WeakMap>(); const disabledButtons = new Set(); -// Utility to check and unwrap proxies -const isProxy = (obj: any) => Object.getPrototypeOf(obj)?.constructor?.name === 'Proxy'; - -const unwrapProxy = (proxy: any) => { - try { - const descriptors = Object.getOwnPropertyDescriptors(proxy); - const unwrapped = Object.fromEntries( - Object.entries(descriptors).reduce>((acc, [key, descriptor]) => { - if (key !== 'Symbol(Symbol.iterator)') { - acc.push([key, descriptor.value]); - } - return acc; - }, []), - ); - - return unwrapped; - } catch (error) { - return proxy; - } -}; - - const getProxyValue = (proxy: any) => { try { if (!proxy || typeof proxy !== 'object') { - return proxy - }; + return proxy; + } - // Handle URLSearchParams-like objects - if (proxy[Symbol.iterator]) { + // Instead of checking for Symbol.iterator, check if it's a URLSearchParams-like object + // by checking for common methods + if (typeof proxy.entries === 'function' && typeof proxy.get === 'function') { try { - return Object.fromEntries(proxy); + // Convert to plain object more safely + const entries = Array.from(proxy.entries() as Iterable<[string, any]>); + return Object.fromEntries(entries); } catch (err) { - // Silent fail + // If conversion fails, return original + return proxy; } } - // Handle standard proxies - if (isProxy(proxy)) { - return unwrapProxy(proxy); + // Instead of checking for proxy, try to safely get own properties + try { + const descriptors = Object.getOwnPropertyDescriptors(proxy); + const unwrapped = Object.fromEntries( + Object.entries(descriptors).reduce>((acc, [key, descriptor]) => { + // Skip Symbol properties and getters that might throw + if (typeof key === 'string' && 'value' in descriptor) { + acc.push([key, descriptor.value]); + } + return acc; + }, []) + ); + + return unwrapped; + } catch (err) { + // If unwrapping fails, return original + return proxy; } - return proxy; } catch (err) { return proxy; } @@ -798,51 +793,51 @@ export const getValueClassName = (value: any) => { }; export const getValuePreview = (value: any) => { - if (Array.isArray(value)) { - return `Array(${value.length})`; - } if (value === null) return 'null'; if (value === undefined) return 'undefined'; - switch (typeof value) { - case 'string': - return `"${value}"`; - case 'number': - return value.toString(); - case 'boolean': - return value.toString(); - case 'object': { - try { - if (value === null) return 'null'; - if (value === undefined) return 'undefined'; - - // Handle special built-in types first - if (value instanceof Promise) return '[Promise]'; - if (value instanceof Set) return `Set(${value.size})`; - if (value instanceof Map) return `Map(${value.size})`; - - // Try to unwrap proxy values - const proto = Object.getPrototypeOf(value); - if (proto?.constructor?.name === 'Proxy') { - const unwrapped = getProxyValue(value); - if (unwrapped !== value) { - const keys = Object.keys(unwrapped); - return `Proxy{${keys.join(', ')}}`; - } - return '[Next.js Params]'; + + try { + if (Array.isArray(value)) { + return `Array(${value.length})`; + } + + switch (typeof value) { + case 'string': + return `"${value}"`; + case 'number': + case 'boolean': + return String(value); + case 'object': { + // Safe checks for common interfaces rather than type detection + if (value.then && typeof value.then === 'function') { + return '[Promise]'; + } + if (value.size !== undefined && value.add && typeof value.add === 'function') { + return `Set(${value.size ?? '?'})`; + } + if (value.size !== undefined && value.set && typeof value.set === 'function') { + return `Map(${value.size ?? '?'})`; + } + if (typeof value.entries === 'function' && typeof value.get === 'function') { + return '[URLSearchParams]'; } // Handle regular objects - const keys = Object.keys(value); - if (keys.length <= 3) { - return `{${keys.join(', ')}}`; + try { + const keys = Object.keys(value); + if (keys.length <= 3) { + return `{${keys.join(', ')}}`; + } + return `{${keys.slice(0, 3).join(', ')}, ...}`; + } catch { + return '{...}'; } - return `{${keys.slice(0, 3).join(', ')}, ...}`; - } catch (error) { - return '{...}'; } + default: + return typeof value; } - default: - return typeof value; + } catch { + return String(value); } };