Skip to content

Commit 4a93a91

Browse files
committed
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.
1 parent e8927ff commit 4a93a91

File tree

1 file changed

+64
-69
lines changed

1 file changed

+64
-69
lines changed

packages/scan/src/core/web/inspect-element/view-state.ts

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,50 +12,45 @@ const EXPANDED_PATHS = new Set<string>();
1212
const fadeOutTimers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();
1313
const disabledButtons = new Set<HTMLButtonElement>();
1414

15-
// Utility to check and unwrap proxies
16-
const isProxy = (obj: any) => Object.getPrototypeOf(obj)?.constructor?.name === 'Proxy';
17-
18-
const unwrapProxy = (proxy: any) => {
19-
try {
20-
const descriptors = Object.getOwnPropertyDescriptors(proxy);
21-
const unwrapped = Object.fromEntries(
22-
Object.entries(descriptors).reduce<Array<[string, any]>>((acc, [key, descriptor]) => {
23-
if (key !== 'Symbol(Symbol.iterator)') {
24-
acc.push([key, descriptor.value]);
25-
}
26-
return acc;
27-
}, []),
28-
);
29-
30-
return unwrapped;
31-
} catch (error) {
32-
return proxy;
33-
}
34-
};
35-
36-
3715

3816
const getProxyValue = (proxy: any) => {
3917
try {
4018
if (!proxy || typeof proxy !== 'object') {
41-
return proxy
42-
};
19+
return proxy;
20+
}
4321

44-
// Handle URLSearchParams-like objects
45-
if (proxy[Symbol.iterator]) {
22+
// Instead of checking for Symbol.iterator, check if it's a URLSearchParams-like object
23+
// by checking for common methods
24+
if (typeof proxy.entries === 'function' && typeof proxy.get === 'function') {
4625
try {
47-
return Object.fromEntries(proxy);
26+
// Convert to plain object more safely
27+
const entries = Array.from(proxy.entries() as Iterable<[string, any]>);
28+
return Object.fromEntries(entries);
4829
} catch (err) {
49-
// Silent fail
30+
// If conversion fails, return original
31+
return proxy;
5032
}
5133
}
5234

53-
// Handle standard proxies
54-
if (isProxy(proxy)) {
55-
return unwrapProxy(proxy);
35+
// Instead of checking for proxy, try to safely get own properties
36+
try {
37+
const descriptors = Object.getOwnPropertyDescriptors(proxy);
38+
const unwrapped = Object.fromEntries(
39+
Object.entries(descriptors).reduce<Array<[string, any]>>((acc, [key, descriptor]) => {
40+
// Skip Symbol properties and getters that might throw
41+
if (typeof key === 'string' && 'value' in descriptor) {
42+
acc.push([key, descriptor.value]);
43+
}
44+
return acc;
45+
}, [])
46+
);
47+
48+
return unwrapped;
49+
} catch (err) {
50+
// If unwrapping fails, return original
51+
return proxy;
5652
}
5753

58-
return proxy;
5954
} catch (err) {
6055
return proxy;
6156
}
@@ -798,51 +793,51 @@ export const getValueClassName = (value: any) => {
798793
};
799794

800795
export const getValuePreview = (value: any) => {
801-
if (Array.isArray(value)) {
802-
return `Array(${value.length})`;
803-
}
804796
if (value === null) return 'null';
805797
if (value === undefined) return 'undefined';
806-
switch (typeof value) {
807-
case 'string':
808-
return `"${value}"`;
809-
case 'number':
810-
return value.toString();
811-
case 'boolean':
812-
return value.toString();
813-
case 'object': {
814-
try {
815-
if (value === null) return 'null';
816-
if (value === undefined) return 'undefined';
817-
818-
// Handle special built-in types first
819-
if (value instanceof Promise) return '[Promise]';
820-
if (value instanceof Set) return `Set(${value.size})`;
821-
if (value instanceof Map) return `Map(${value.size})`;
822-
823-
// Try to unwrap proxy values
824-
const proto = Object.getPrototypeOf(value);
825-
if (proto?.constructor?.name === 'Proxy') {
826-
const unwrapped = getProxyValue(value);
827-
if (unwrapped !== value) {
828-
const keys = Object.keys(unwrapped);
829-
return `Proxy{${keys.join(', ')}}`;
830-
}
831-
return '[Next.js Params]';
798+
799+
try {
800+
if (Array.isArray(value)) {
801+
return `Array(${value.length})`;
802+
}
803+
804+
switch (typeof value) {
805+
case 'string':
806+
return `"${value}"`;
807+
case 'number':
808+
case 'boolean':
809+
return String(value);
810+
case 'object': {
811+
// Safe checks for common interfaces rather than type detection
812+
if (value.then && typeof value.then === 'function') {
813+
return '[Promise]';
814+
}
815+
if (value.size !== undefined && value.add && typeof value.add === 'function') {
816+
return `Set(${value.size ?? '?'})`;
817+
}
818+
if (value.size !== undefined && value.set && typeof value.set === 'function') {
819+
return `Map(${value.size ?? '?'})`;
820+
}
821+
if (typeof value.entries === 'function' && typeof value.get === 'function') {
822+
return '[URLSearchParams]';
832823
}
833824

834825
// Handle regular objects
835-
const keys = Object.keys(value);
836-
if (keys.length <= 3) {
837-
return `{${keys.join(', ')}}`;
826+
try {
827+
const keys = Object.keys(value);
828+
if (keys.length <= 3) {
829+
return `{${keys.join(', ')}}`;
830+
}
831+
return `{${keys.slice(0, 3).join(', ')}, ...}`;
832+
} catch {
833+
return '{...}';
838834
}
839-
return `{${keys.slice(0, 3).join(', ')}, ...}`;
840-
} catch (error) {
841-
return '{...}';
842835
}
836+
default:
837+
return typeof value;
843838
}
844-
default:
845-
return typeof value;
839+
} catch {
840+
return String(value);
846841
}
847842
};
848843

0 commit comments

Comments
 (0)