@domscribe/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter/react-adapter.d.ts +121 -0
- package/adapter/react-adapter.d.ts.map +1 -0
- package/adapter/react-adapter.js +486 -0
- package/adapter/types.d.ts +131 -0
- package/adapter/types.d.ts.map +1 -0
- package/adapter/types.js +17 -0
- package/auto-init.d.ts +2 -0
- package/auto-init.d.ts.map +1 -0
- package/auto-init.js +40 -0
- package/component/component-name-resolver.d.ts +118 -0
- package/component/component-name-resolver.d.ts.map +1 -0
- package/component/component-name-resolver.js +361 -0
- package/component/props-extractor.d.ts +51 -0
- package/component/props-extractor.d.ts.map +1 -0
- package/component/props-extractor.js +122 -0
- package/component/state-extractor.d.ts +60 -0
- package/component/state-extractor.d.ts.map +1 -0
- package/component/state-extractor.js +162 -0
- package/component/types.d.ts +256 -0
- package/component/types.d.ts.map +1 -0
- package/component/types.js +5 -0
- package/errors/index.d.ts +36 -0
- package/errors/index.d.ts.map +1 -0
- package/errors/index.js +75 -0
- package/fiber/fiber-walker.d.ts +58 -0
- package/fiber/fiber-walker.d.ts.map +1 -0
- package/fiber/fiber-walker.js +118 -0
- package/fiber/types.d.ts +162 -0
- package/fiber/types.d.ts.map +1 -0
- package/fiber/types.js +32 -0
- package/index.d.ts +14 -0
- package/index.d.ts.map +1 -0
- package/index.js +15 -0
- package/package.json +59 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/utils/constants.d.ts +94 -0
- package/utils/constants.d.ts.map +1 -0
- package/utils/constants.js +123 -0
- package/utils/type-guards.d.ts +31 -0
- package/utils/type-guards.d.ts.map +1 -0
- package/utils/type-guards.js +89 -0
- package/utils/types.d.ts +6 -0
- package/utils/types.d.ts.map +1 -0
- package/utils/types.js +5 -0
- package/vite/index.d.ts +7 -0
- package/vite/index.d.ts.map +1 -0
- package/vite/index.js +5 -0
- package/vite/types.d.ts +56 -0
- package/vite/types.d.ts.map +1 -0
- package/vite/types.js +10 -0
- package/vite/vite-plugin.d.ts +29 -0
- package/vite/vite-plugin.d.ts.map +1 -0
- package/vite/vite-plugin.js +104 -0
- package/webpack/index.d.ts +7 -0
- package/webpack/index.d.ts.map +1 -0
- package/webpack/index.js +5 -0
- package/webpack/webpack-plugin.d.ts +50 -0
- package/webpack/webpack-plugin.d.ts.map +1 -0
- package/webpack/webpack-plugin.js +63 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactAdapter - React framework adapter for Domscribe
|
|
3
|
+
*
|
|
4
|
+
* Implements the FrameworkAdapter interface to provide runtime context capture
|
|
5
|
+
* for React applications. Supports multiple capture strategies:
|
|
6
|
+
* - DevTools: Use React DevTools hook (most reliable, requires DevTools)
|
|
7
|
+
* - Fiber: Direct Fiber tree access (fast, but uses React internal API)
|
|
8
|
+
* - Best-effort: Try multiple strategies in order of reliability
|
|
9
|
+
*
|
|
10
|
+
* @module @domscribe/react/adapter/react-adapter
|
|
11
|
+
*/
|
|
12
|
+
import type { ComponentTreeNode } from '@domscribe/runtime';
|
|
13
|
+
import type { ReactAdapterOptions, ReactFrameworkAdapter } from './types.js';
|
|
14
|
+
import { CaptureStrategy } from './types.js';
|
|
15
|
+
import type { Nullable } from '../utils/types.js';
|
|
16
|
+
/**
|
|
17
|
+
* ReactAdapter class implementing FrameworkAdapter interface
|
|
18
|
+
*/
|
|
19
|
+
export declare class ReactAdapter implements ReactFrameworkAdapter {
|
|
20
|
+
readonly name = "react";
|
|
21
|
+
readonly version?: string;
|
|
22
|
+
private _version?;
|
|
23
|
+
private state;
|
|
24
|
+
private options;
|
|
25
|
+
private fiberWalker;
|
|
26
|
+
private nameResolver;
|
|
27
|
+
private propsExtractor;
|
|
28
|
+
private stateExtractor;
|
|
29
|
+
constructor(options?: ReactAdapterOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Detect React version from global React object or DevTools
|
|
32
|
+
*/
|
|
33
|
+
private detectReactVersion;
|
|
34
|
+
/**
|
|
35
|
+
* Check if React DevTools hook is available
|
|
36
|
+
*/
|
|
37
|
+
private checkDevToolsAvailability;
|
|
38
|
+
/**
|
|
39
|
+
* Get the React DevTools hook object
|
|
40
|
+
*/
|
|
41
|
+
private getDevToolsHook;
|
|
42
|
+
/**
|
|
43
|
+
* Check if we can access Fiber internals
|
|
44
|
+
*/
|
|
45
|
+
private checkFiberAccess;
|
|
46
|
+
/**
|
|
47
|
+
* Get component instance from a DOM element
|
|
48
|
+
*
|
|
49
|
+
* @param element - The DOM element to query
|
|
50
|
+
* @returns Component instance (Fiber node) or null if not found
|
|
51
|
+
*/
|
|
52
|
+
getComponentInstance(element: HTMLElement): Nullable<unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Capture component props
|
|
55
|
+
*
|
|
56
|
+
* @param component - The component instance (Fiber node)
|
|
57
|
+
* @returns Serialized props object or null
|
|
58
|
+
*/
|
|
59
|
+
captureProps(component: unknown): Nullable<Record<string, unknown>>;
|
|
60
|
+
/**
|
|
61
|
+
* Capture component state
|
|
62
|
+
*
|
|
63
|
+
* @param component - The component instance (Fiber node)
|
|
64
|
+
* @returns Serialized state object or null
|
|
65
|
+
*/
|
|
66
|
+
captureState(component: unknown): Nullable<Record<string, unknown>>;
|
|
67
|
+
/**
|
|
68
|
+
* Get component name
|
|
69
|
+
*
|
|
70
|
+
* @param component - The component instance (Fiber node)
|
|
71
|
+
* @returns Component name or null
|
|
72
|
+
*/
|
|
73
|
+
getComponentName(component: unknown): Nullable<string>;
|
|
74
|
+
/**
|
|
75
|
+
* Get component tree
|
|
76
|
+
*
|
|
77
|
+
* @param component - The component instance (Fiber node)
|
|
78
|
+
* @returns Component tree node or null
|
|
79
|
+
*/
|
|
80
|
+
getComponentTree(component: unknown): Nullable<ComponentTreeNode>;
|
|
81
|
+
/**
|
|
82
|
+
* Get the active capture strategy
|
|
83
|
+
*/
|
|
84
|
+
getActiveStrategy(): CaptureStrategy;
|
|
85
|
+
/**
|
|
86
|
+
* Get the React version
|
|
87
|
+
*/
|
|
88
|
+
getReactVersion(): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Check if React DevTools is available
|
|
91
|
+
*/
|
|
92
|
+
hasDevToolsAccess(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve component from DOM element using active strategy
|
|
95
|
+
*/
|
|
96
|
+
private resolveComponent;
|
|
97
|
+
/**
|
|
98
|
+
* Resolve component via React DevTools hook
|
|
99
|
+
*/
|
|
100
|
+
private resolveViaDevTools;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve component via direct Fiber access
|
|
103
|
+
*/
|
|
104
|
+
private resolveViaFiber;
|
|
105
|
+
/**
|
|
106
|
+
* Get Fiber node from DOM element using internal React keys
|
|
107
|
+
*/
|
|
108
|
+
private getFiberFromElement;
|
|
109
|
+
/**
|
|
110
|
+
* Build component tree from a Fiber node
|
|
111
|
+
*/
|
|
112
|
+
private buildComponentTree;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a ReactAdapter instance
|
|
116
|
+
*
|
|
117
|
+
* @param options - Adapter options
|
|
118
|
+
* @returns ReactAdapter instance
|
|
119
|
+
*/
|
|
120
|
+
export declare function createReactAdapter(options?: ReactAdapterOptions): ReactAdapter;
|
|
121
|
+
//# sourceMappingURL=react-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-adapter.d.ts","sourceRoot":"","sources":["../../../../packages/domscribe-react/src/adapter/react-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,KAAK,EACV,mBAAmB,EAGnB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAM7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AASlD;;GAEG;AACH,qBAAa,YAAa,YAAW,qBAAqB;IACxD,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,OAAO,CAAgC;IAG/C,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,cAAc,CAAiB;gBAE3B,OAAO,CAAC,EAAE,mBAAmB;IAuDzC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAkC1B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IASjC;;OAEG;IACH,OAAO,CAAC,eAAe;IAgBvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA4BxB;;;;;OAKG;IACH,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC;IAmB7D;;;;;OAKG;IACH,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmBnE;;;;;OAKG;IACH,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmBnE;;;;;OAKG;IACH,gBAAgB,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;IAkBtD;;;;;OAKG;IACH,gBAAgB,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC,iBAAiB,CAAC;IAmBjE;;OAEG;IACH,iBAAiB,IAAI,eAAe;IAIpC;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAQ5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6C1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAoCvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAuE3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,YAAY,CAEd"}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactAdapter - React framework adapter for Domscribe
|
|
3
|
+
*
|
|
4
|
+
* Implements the FrameworkAdapter interface to provide runtime context capture
|
|
5
|
+
* for React applications. Supports multiple capture strategies:
|
|
6
|
+
* - DevTools: Use React DevTools hook (most reliable, requires DevTools)
|
|
7
|
+
* - Fiber: Direct Fiber tree access (fast, but uses React internal API)
|
|
8
|
+
* - Best-effort: Try multiple strategies in order of reliability
|
|
9
|
+
*
|
|
10
|
+
* @module @domscribe/react/adapter/react-adapter
|
|
11
|
+
*/
|
|
12
|
+
import { CaptureStrategy } from './types.js';
|
|
13
|
+
import { FiberWalker } from '../fiber/fiber-walker.js';
|
|
14
|
+
import { ComponentNameResolver } from '../component/component-name-resolver.js';
|
|
15
|
+
import { PropsExtractor } from '../component/props-extractor.js';
|
|
16
|
+
import { StateExtractor } from '../component/state-extractor.js';
|
|
17
|
+
import { DEVTOOLS_HOOK_KEY, REACT_ELEMENT_KEYS } from '../utils/constants.js';
|
|
18
|
+
import { isReactFiber, hasReactFiberKey, isComponentFiber, isReactDevToolsHook, } from '../utils/type-guards.js';
|
|
19
|
+
import { FiberAccessError, ComponentResolutionError } from '../errors/index.js';
|
|
20
|
+
/**
|
|
21
|
+
* ReactAdapter class implementing FrameworkAdapter interface
|
|
22
|
+
*/
|
|
23
|
+
export class ReactAdapter {
|
|
24
|
+
name = 'react';
|
|
25
|
+
version;
|
|
26
|
+
_version;
|
|
27
|
+
state;
|
|
28
|
+
options;
|
|
29
|
+
// Utilities
|
|
30
|
+
fiberWalker;
|
|
31
|
+
nameResolver;
|
|
32
|
+
propsExtractor;
|
|
33
|
+
stateExtractor;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
// Initialize utilities
|
|
36
|
+
this.fiberWalker = new FiberWalker();
|
|
37
|
+
this.nameResolver = new ComponentNameResolver();
|
|
38
|
+
this.propsExtractor = new PropsExtractor();
|
|
39
|
+
this.stateExtractor = new StateExtractor();
|
|
40
|
+
// Normalize options with defaults
|
|
41
|
+
this.options = {
|
|
42
|
+
strategy: options?.strategy ?? CaptureStrategy.BEST_EFFORT,
|
|
43
|
+
maxTreeDepth: options?.maxTreeDepth ?? 50,
|
|
44
|
+
debug: options?.debug ?? false,
|
|
45
|
+
includeWrappers: options?.includeWrappers ?? true,
|
|
46
|
+
hookNameResolvers: options?.hookNameResolvers ?? new Map(),
|
|
47
|
+
};
|
|
48
|
+
// Initialize state
|
|
49
|
+
this.state = {
|
|
50
|
+
initialized: false,
|
|
51
|
+
hasDevTools: false,
|
|
52
|
+
hasFiberAccess: false,
|
|
53
|
+
activeStrategy: this.options.strategy,
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
// Detect React version
|
|
57
|
+
this._version = this.detectReactVersion();
|
|
58
|
+
this.state.version = this._version;
|
|
59
|
+
// Set readonly version property
|
|
60
|
+
Object.defineProperty(this, 'version', {
|
|
61
|
+
value: this._version,
|
|
62
|
+
writable: false,
|
|
63
|
+
configurable: false,
|
|
64
|
+
});
|
|
65
|
+
// Check for DevTools hook
|
|
66
|
+
this.state.hasDevTools = this.checkDevToolsAvailability();
|
|
67
|
+
// Check for Fiber access
|
|
68
|
+
this.state.hasFiberAccess = this.checkFiberAccess();
|
|
69
|
+
this.state.initialized = true;
|
|
70
|
+
if (this.options.debug) {
|
|
71
|
+
console.log('[ReactAdapter] Initialized:', this.state);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (this.options.debug) {
|
|
76
|
+
console.error('[ReactAdapter] Initialization failed:', error);
|
|
77
|
+
}
|
|
78
|
+
this.state.initialized = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Detect React version from global React object or DevTools
|
|
83
|
+
*/
|
|
84
|
+
detectReactVersion() {
|
|
85
|
+
// Try React.version
|
|
86
|
+
if (typeof window !== 'undefined' && 'React' in window) {
|
|
87
|
+
const React = window
|
|
88
|
+
.React;
|
|
89
|
+
if (React?.version) {
|
|
90
|
+
return React.version;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Try DevTools hook
|
|
94
|
+
if (this.state.hasDevTools) {
|
|
95
|
+
const hook = this.getDevToolsHook();
|
|
96
|
+
if (typeof hook === 'object' &&
|
|
97
|
+
hook !== null &&
|
|
98
|
+
'rendererInterfaces' in hook) {
|
|
99
|
+
const interfaces = hook.rendererInterfaces;
|
|
100
|
+
const firstRenderer = interfaces.values().next().value;
|
|
101
|
+
if (firstRenderer &&
|
|
102
|
+
typeof firstRenderer === 'object' &&
|
|
103
|
+
firstRenderer !== null &&
|
|
104
|
+
'version' in firstRenderer) {
|
|
105
|
+
return String(firstRenderer.version);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if React DevTools hook is available
|
|
113
|
+
*/
|
|
114
|
+
checkDevToolsAvailability() {
|
|
115
|
+
if (typeof window === 'undefined') {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const hook = this.getDevToolsHook();
|
|
119
|
+
return hook !== null && typeof hook === 'object';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get the React DevTools hook object
|
|
123
|
+
*/
|
|
124
|
+
getDevToolsHook() {
|
|
125
|
+
if (typeof window === 'undefined') {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const hook = window[DEVTOOLS_HOOK_KEY];
|
|
129
|
+
if (isReactDevToolsHook(hook)) {
|
|
130
|
+
return hook;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if we can access Fiber internals
|
|
136
|
+
*/
|
|
137
|
+
checkFiberAccess() {
|
|
138
|
+
// Try to find a React root in the document
|
|
139
|
+
if (typeof document === 'undefined') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// Look for React root nodes
|
|
143
|
+
const reactRoots = document.querySelectorAll('[data-reactroot]');
|
|
144
|
+
if (reactRoots.length > 0) {
|
|
145
|
+
const rootElement = reactRoots[0];
|
|
146
|
+
return hasReactFiberKey(rootElement);
|
|
147
|
+
}
|
|
148
|
+
// Try finding any element with React Fiber keys
|
|
149
|
+
const allElements = document.querySelectorAll('*');
|
|
150
|
+
for (let i = 0; i < Math.min(allElements.length, 100); i++) {
|
|
151
|
+
if (hasReactFiberKey(allElements[i])) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// FrameworkAdapter Interface Implementation
|
|
159
|
+
// ============================================================================
|
|
160
|
+
/**
|
|
161
|
+
* Get component instance from a DOM element
|
|
162
|
+
*
|
|
163
|
+
* @param element - The DOM element to query
|
|
164
|
+
* @returns Component instance (Fiber node) or null if not found
|
|
165
|
+
*/
|
|
166
|
+
getComponentInstance(element) {
|
|
167
|
+
if (!this.state.initialized) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const result = this.resolveComponent(element);
|
|
172
|
+
if (result.success && result.component) {
|
|
173
|
+
return result.component;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (this.options.debug) {
|
|
179
|
+
console.error('[ReactAdapter] getComponentInstance failed:', error);
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Capture component props
|
|
186
|
+
*
|
|
187
|
+
* @param component - The component instance (Fiber node)
|
|
188
|
+
* @returns Serialized props object or null
|
|
189
|
+
*/
|
|
190
|
+
captureProps(component) {
|
|
191
|
+
if (!this.state.initialized || !isReactFiber(component)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const result = this.propsExtractor.extract(component);
|
|
196
|
+
if (result.success && result.props) {
|
|
197
|
+
return result.props;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (this.options.debug) {
|
|
203
|
+
console.error('[ReactAdapter] captureProps failed:', error);
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Capture component state
|
|
210
|
+
*
|
|
211
|
+
* @param component - The component instance (Fiber node)
|
|
212
|
+
* @returns Serialized state object or null
|
|
213
|
+
*/
|
|
214
|
+
captureState(component) {
|
|
215
|
+
if (!this.state.initialized || !isReactFiber(component)) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const result = this.stateExtractor.extract(component);
|
|
220
|
+
if (result.success && result.state) {
|
|
221
|
+
return result.state;
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (this.options.debug) {
|
|
227
|
+
console.error('[ReactAdapter] captureState failed:', error);
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get component name
|
|
234
|
+
*
|
|
235
|
+
* @param component - The component instance (Fiber node)
|
|
236
|
+
* @returns Component name or null
|
|
237
|
+
*/
|
|
238
|
+
getComponentName(component) {
|
|
239
|
+
if (!this.state.initialized || !isReactFiber(component)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const result = this.nameResolver.resolve(component, {
|
|
244
|
+
includeWrappers: this.options.includeWrappers,
|
|
245
|
+
});
|
|
246
|
+
return result.name || null;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (this.options.debug) {
|
|
250
|
+
console.error('[ReactAdapter] getComponentName failed:', error);
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get component tree
|
|
257
|
+
*
|
|
258
|
+
* @param component - The component instance (Fiber node)
|
|
259
|
+
* @returns Component tree node or null
|
|
260
|
+
*/
|
|
261
|
+
getComponentTree(component) {
|
|
262
|
+
if (!this.state.initialized || !isReactFiber(component)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
return this.buildComponentTree(component);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (this.options.debug) {
|
|
270
|
+
console.error('[ReactAdapter] getComponentTree failed:', error);
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// React-Specific Methods
|
|
277
|
+
// ============================================================================
|
|
278
|
+
/**
|
|
279
|
+
* Get the active capture strategy
|
|
280
|
+
*/
|
|
281
|
+
getActiveStrategy() {
|
|
282
|
+
return this.state.activeStrategy;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the React version
|
|
286
|
+
*/
|
|
287
|
+
getReactVersion() {
|
|
288
|
+
return this._version ?? null;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if React DevTools is available
|
|
292
|
+
*/
|
|
293
|
+
hasDevToolsAccess() {
|
|
294
|
+
return this.state.hasDevTools;
|
|
295
|
+
}
|
|
296
|
+
// ============================================================================
|
|
297
|
+
// Private Helper Methods
|
|
298
|
+
// ============================================================================
|
|
299
|
+
/**
|
|
300
|
+
* Resolve component from DOM element using active strategy
|
|
301
|
+
*/
|
|
302
|
+
resolveComponent(element) {
|
|
303
|
+
// Try strategies in order based on active strategy
|
|
304
|
+
if (this.state.activeStrategy === CaptureStrategy.DEVTOOLS) {
|
|
305
|
+
return this.resolveViaDevTools(element);
|
|
306
|
+
}
|
|
307
|
+
else if (this.state.activeStrategy === CaptureStrategy.FIBER) {
|
|
308
|
+
return this.resolveViaFiber(element);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// Best-effort: try both
|
|
312
|
+
const devToolsResult = this.resolveViaDevTools(element);
|
|
313
|
+
if (devToolsResult.success) {
|
|
314
|
+
return devToolsResult;
|
|
315
|
+
}
|
|
316
|
+
return this.resolveViaFiber(element);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Resolve component via React DevTools hook
|
|
321
|
+
*/
|
|
322
|
+
resolveViaDevTools(element) {
|
|
323
|
+
try {
|
|
324
|
+
const hook = this.getDevToolsHook();
|
|
325
|
+
if (!hook) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: new ComponentResolutionError('DevTools hook not available'),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
for (const renderer of hook.renderers.values()) {
|
|
332
|
+
if (renderer?.findFiberByHostInstance) {
|
|
333
|
+
const fiber = renderer.findFiberByHostInstance(element);
|
|
334
|
+
if (isReactFiber(fiber)) {
|
|
335
|
+
// Find nearest component Fiber (not host Fiber)
|
|
336
|
+
const componentFiber = this.fiberWalker.findNearestComponentFiber(fiber);
|
|
337
|
+
if (componentFiber) {
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
component: componentFiber,
|
|
341
|
+
strategy: CaptureStrategy.DEVTOOLS,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
error: new ComponentResolutionError('Could not resolve Fiber via DevTools'),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
error: error instanceof Error
|
|
356
|
+
? error
|
|
357
|
+
: new ComponentResolutionError('DevTools resolution failed'),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Resolve component via direct Fiber access
|
|
363
|
+
*/
|
|
364
|
+
resolveViaFiber(element) {
|
|
365
|
+
try {
|
|
366
|
+
// Get Fiber node from element
|
|
367
|
+
const fiber = this.getFiberFromElement(element);
|
|
368
|
+
if (!fiber) {
|
|
369
|
+
return {
|
|
370
|
+
success: false,
|
|
371
|
+
error: new FiberAccessError('Could not find Fiber on element'),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// Find nearest component Fiber
|
|
375
|
+
const componentFiber = this.fiberWalker.findNearestComponentFiber(fiber);
|
|
376
|
+
if (!componentFiber) {
|
|
377
|
+
return {
|
|
378
|
+
success: false,
|
|
379
|
+
error: new FiberAccessError('Could not find component Fiber'),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
success: true,
|
|
384
|
+
component: componentFiber,
|
|
385
|
+
strategy: CaptureStrategy.FIBER,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
return {
|
|
390
|
+
success: false,
|
|
391
|
+
error: error instanceof Error
|
|
392
|
+
? error
|
|
393
|
+
: new FiberAccessError('Fiber resolution failed'),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get Fiber node from DOM element using internal React keys
|
|
399
|
+
*/
|
|
400
|
+
getFiberFromElement(element) {
|
|
401
|
+
// React attaches Fiber to DOM elements using internal keys
|
|
402
|
+
// These keys vary by React version:
|
|
403
|
+
// - React 16-17: __reactInternalInstance$...
|
|
404
|
+
// - React 18+: __reactFiber$...
|
|
405
|
+
const keys = Object.keys(element);
|
|
406
|
+
const fiberKey = keys.find((key) => key.startsWith(REACT_ELEMENT_KEYS.FIBER_16) ||
|
|
407
|
+
key.startsWith(REACT_ELEMENT_KEYS.FIBER_17_18));
|
|
408
|
+
if (!fiberKey) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const fiber = element[fiberKey];
|
|
412
|
+
if (isReactFiber(fiber)) {
|
|
413
|
+
return fiber;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Build component tree from a Fiber node
|
|
419
|
+
*/
|
|
420
|
+
buildComponentTree(fiber, depth = 0) {
|
|
421
|
+
if (depth >= this.options.maxTreeDepth) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const name = this.getComponentName(fiber);
|
|
426
|
+
if (!name) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const node = {
|
|
430
|
+
name,
|
|
431
|
+
instance: fiber,
|
|
432
|
+
};
|
|
433
|
+
// Add props
|
|
434
|
+
const props = this.captureProps(fiber);
|
|
435
|
+
if (props) {
|
|
436
|
+
node.props = props;
|
|
437
|
+
}
|
|
438
|
+
// Add state
|
|
439
|
+
const state = this.captureState(fiber);
|
|
440
|
+
if (state) {
|
|
441
|
+
node.state = state;
|
|
442
|
+
}
|
|
443
|
+
// Add parent if within depth limit
|
|
444
|
+
if (fiber.return) {
|
|
445
|
+
const parentFiber = this.fiberWalker.findNearestComponentFiber(fiber.return);
|
|
446
|
+
if (parentFiber) {
|
|
447
|
+
const parentNode = this.buildComponentTree(parentFiber, depth + 1);
|
|
448
|
+
if (parentNode) {
|
|
449
|
+
node.parent = parentNode;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Add children if within depth limit
|
|
454
|
+
if (depth + 1 < this.options.maxTreeDepth) {
|
|
455
|
+
const allChildren = this.fiberWalker.getChildren(fiber);
|
|
456
|
+
// Filter to only component fibers
|
|
457
|
+
const componentChildren = allChildren.filter((child) => isComponentFiber(child));
|
|
458
|
+
if (componentChildren.length > 0) {
|
|
459
|
+
node.children = [];
|
|
460
|
+
for (const child of componentChildren) {
|
|
461
|
+
const childNode = this.buildComponentTree(child, depth + 1);
|
|
462
|
+
if (childNode) {
|
|
463
|
+
node.children.push(childNode);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return node;
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
if (this.options.debug) {
|
|
472
|
+
console.error('[ReactAdapter] buildComponentTree failed:', error);
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Create a ReactAdapter instance
|
|
480
|
+
*
|
|
481
|
+
* @param options - Adapter options
|
|
482
|
+
* @returns ReactAdapter instance
|
|
483
|
+
*/
|
|
484
|
+
export function createReactAdapter(options) {
|
|
485
|
+
return new ReactAdapter(options);
|
|
486
|
+
}
|