@ansight/react-native 1.0.2-preview.1
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/AnsightReactNative.podspec +20 -0
- package/LICENSE +200 -0
- package/README.md +436 -0
- package/android/build.gradle +70 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/CMakeLists.txt +16 -0
- package/android/src/main/cpp/ansight_react_native_memory.cpp +57 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativeModule.kt +1353 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativePackage.kt +14 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/ReactNativeMemoryProfiler.kt +167 -0
- package/index.d.ts +633 -0
- package/index.js +2303 -0
- package/ios/AnsightReactNative.swift +1635 -0
- package/ios/AnsightReactNativeMemorySampler.mm +130 -0
- package/ios/AnsightReactNativeModule.m +167 -0
- package/package.json +50 -0
- package/react-native.config.js +12 -0
- package/tsconfig.json +12 -0
package/index.js
ADDED
|
@@ -0,0 +1,2303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { AppState, NativeEventEmitter, NativeModules, Platform, UIManager, findNodeHandle } = require("react-native");
|
|
4
|
+
|
|
5
|
+
const nativeModule = NativeModules.AnsightReactNative;
|
|
6
|
+
|
|
7
|
+
if (!nativeModule) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"NativeModules.AnsightReactNative is not available. Rebuild the native app after installing @ansight/react-native."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const toolHandlers = new Map();
|
|
14
|
+
const artifactProviders = new Map();
|
|
15
|
+
let toolEventSubscription = null;
|
|
16
|
+
let appStateSubscription = null;
|
|
17
|
+
let reactToolRegistrations = [];
|
|
18
|
+
let artifactToolRegistrations = [];
|
|
19
|
+
let reactToolNavigationRef = null;
|
|
20
|
+
let nextReactNodeId = 1;
|
|
21
|
+
const reactNodeIds = new WeakMap();
|
|
22
|
+
let reactFiberById = new Map();
|
|
23
|
+
const hostConnectionStatusListeners = new Set();
|
|
24
|
+
let lastHostConnectionStatusKey = null;
|
|
25
|
+
const logListeners = new Set();
|
|
26
|
+
let logEventSubscription = null;
|
|
27
|
+
const REACT_COMPONENT_TREE_TOOL_ID = "react.get_component_tree";
|
|
28
|
+
const REACT_SHADOW_TREE_TOOL_ID = "react.get_shadow_tree";
|
|
29
|
+
|
|
30
|
+
function normalizePairingPayload(payload) {
|
|
31
|
+
if (payload == null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeOptions(options = {}) {
|
|
38
|
+
const normalized = { ...options };
|
|
39
|
+
if (normalized.pairingConfig != null) {
|
|
40
|
+
normalized.pairingConfigJson = normalizePairingPayload(normalized.pairingConfig);
|
|
41
|
+
delete normalized.pairingConfig;
|
|
42
|
+
}
|
|
43
|
+
if (normalized.bundledPairingConfig != null) {
|
|
44
|
+
normalized.pairingConfigJson = normalizePairingPayload(normalized.bundledPairingConfig);
|
|
45
|
+
delete normalized.bundledPairingConfig;
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function cloneOptions(options = {}) {
|
|
51
|
+
const clone = { ...options };
|
|
52
|
+
if (options.defaultMemoryChannels) {
|
|
53
|
+
clone.defaultMemoryChannels = { ...options.defaultMemoryChannels };
|
|
54
|
+
}
|
|
55
|
+
if (options.reactNativeMemory && typeof options.reactNativeMemory === "object") {
|
|
56
|
+
clone.reactNativeMemory = { ...options.reactNativeMemory };
|
|
57
|
+
}
|
|
58
|
+
if (options.sessionJpegCapture && typeof options.sessionJpegCapture === "object") {
|
|
59
|
+
clone.sessionJpegCapture = { ...options.sessionJpegCapture };
|
|
60
|
+
}
|
|
61
|
+
if (options.touchCapture && typeof options.touchCapture === "object") {
|
|
62
|
+
clone.touchCapture = { ...options.touchCapture };
|
|
63
|
+
}
|
|
64
|
+
if (options.lifecycleCapture) {
|
|
65
|
+
clone.lifecycleCapture = { ...options.lifecycleCapture };
|
|
66
|
+
}
|
|
67
|
+
if (options.hostAutoProbe) {
|
|
68
|
+
clone.hostAutoProbe = { ...options.hostAutoProbe };
|
|
69
|
+
}
|
|
70
|
+
if (options.hostConnection) {
|
|
71
|
+
clone.hostConnection = { ...options.hostConnection };
|
|
72
|
+
}
|
|
73
|
+
if (options.secureStorage) {
|
|
74
|
+
clone.secureStorage = {
|
|
75
|
+
...options.secureStorage,
|
|
76
|
+
allowedKeys: options.secureStorage.allowedKeys ? [...options.secureStorage.allowedKeys] : undefined,
|
|
77
|
+
allowedPrefixes: options.secureStorage.allowedPrefixes ? [...options.secureStorage.allowedPrefixes] : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (options.remoteTools) {
|
|
81
|
+
clone.remoteTools = cloneRemoteToolsOptions(options.remoteTools);
|
|
82
|
+
}
|
|
83
|
+
if (options.additionalChannels) {
|
|
84
|
+
clone.additionalChannels = [...options.additionalChannels];
|
|
85
|
+
}
|
|
86
|
+
if (options.customProperties) {
|
|
87
|
+
clone.customProperties = Object.fromEntries(
|
|
88
|
+
Object.entries(options.customProperties).map(([group, properties]) => [group, { ...(properties || {}) }])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return clone;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function cloneRoots(roots) {
|
|
95
|
+
return roots ? roots.map((root) => ({ ...root })) : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function cloneSecureStorageToolsOptions(options = {}) {
|
|
99
|
+
return {
|
|
100
|
+
...options,
|
|
101
|
+
allowedKeys: options.allowedKeys ? [...options.allowedKeys] : undefined,
|
|
102
|
+
allowedKeyPrefixes: options.allowedKeyPrefixes ? [...options.allowedKeyPrefixes] : undefined,
|
|
103
|
+
allowedPrefixes: options.allowedPrefixes ? [...options.allowedPrefixes] : undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function cloneVisualTreeToolsOptions(options) {
|
|
108
|
+
if (options == null) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (typeof options === "boolean") {
|
|
112
|
+
return options;
|
|
113
|
+
}
|
|
114
|
+
if (typeof options === "object") {
|
|
115
|
+
return { ...options };
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function cloneRemoteToolsOptions(remoteTools = {}) {
|
|
121
|
+
return {
|
|
122
|
+
...remoteTools,
|
|
123
|
+
visualTree: cloneVisualTreeToolsOptions(remoteTools.visualTree),
|
|
124
|
+
fileSystem: remoteTools.fileSystem ? {
|
|
125
|
+
...remoteTools.fileSystem,
|
|
126
|
+
additionalRoots: cloneRoots(remoteTools.fileSystem.additionalRoots),
|
|
127
|
+
} : undefined,
|
|
128
|
+
database: remoteTools.database ? {
|
|
129
|
+
...remoteTools.database,
|
|
130
|
+
additionalRoots: cloneRoots(remoteTools.database.additionalRoots),
|
|
131
|
+
} : undefined,
|
|
132
|
+
preferences: remoteTools.preferences ? {
|
|
133
|
+
...remoteTools.preferences,
|
|
134
|
+
allowedStores: remoteTools.preferences.allowedStores ? [...remoteTools.preferences.allowedStores] : undefined,
|
|
135
|
+
allowedKeys: remoteTools.preferences.allowedKeys ? [...remoteTools.preferences.allowedKeys] : undefined,
|
|
136
|
+
allowedKeyPrefixes: remoteTools.preferences.allowedKeyPrefixes ? [...remoteTools.preferences.allowedKeyPrefixes] : undefined,
|
|
137
|
+
} : undefined,
|
|
138
|
+
reflection: remoteTools.reflection ? {
|
|
139
|
+
...remoteTools.reflection,
|
|
140
|
+
allowedRootIds: remoteTools.reflection.allowedRootIds ? [...remoteTools.reflection.allowedRootIds] : undefined,
|
|
141
|
+
allowedTypePrefixes: remoteTools.reflection.allowedTypePrefixes ? [...remoteTools.reflection.allowedTypePrefixes] : undefined,
|
|
142
|
+
} : undefined,
|
|
143
|
+
secureStorage: remoteTools.secureStorage ? cloneSecureStorageToolsOptions(remoteTools.secureStorage) : undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class AnsightOptionsBuilder {
|
|
148
|
+
constructor(options = {}) {
|
|
149
|
+
this._options = cloneOptions(options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
withAnsightDefaults() {
|
|
153
|
+
this._options = {
|
|
154
|
+
...this._options,
|
|
155
|
+
useNativeAllInOneDefaults: true,
|
|
156
|
+
sampleFrequencyMilliseconds: 400,
|
|
157
|
+
retentionPeriodSeconds: 120,
|
|
158
|
+
enableFramesPerSecond: true,
|
|
159
|
+
enableBatteryLevel: false,
|
|
160
|
+
sessionJpegCapture: {
|
|
161
|
+
intervalMilliseconds: 2000,
|
|
162
|
+
quality: 60,
|
|
163
|
+
maxWidth: 480,
|
|
164
|
+
},
|
|
165
|
+
touchCapture: {},
|
|
166
|
+
toolGuard: "readOnly",
|
|
167
|
+
hostAutoProbe: {
|
|
168
|
+
...(this._options.hostAutoProbe || {}),
|
|
169
|
+
enabled: true,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
withNativeAllInOneDefaults() {
|
|
176
|
+
return this.withAnsightDefaults();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
withAnsightSdk(configure) {
|
|
180
|
+
this.withAnsightDefaults().withAllToolAccess();
|
|
181
|
+
if (typeof configure === "function") {
|
|
182
|
+
configure(this);
|
|
183
|
+
}
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
withSampleFrequencyMilliseconds(sampleFrequencyMilliseconds) {
|
|
188
|
+
this._options.sampleFrequencyMilliseconds = sampleFrequencyMilliseconds;
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
withFramesPerSecond() {
|
|
193
|
+
this._options.enableFramesPerSecond = true;
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
withoutFramesPerSecond() {
|
|
198
|
+
this._options.enableFramesPerSecond = false;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
withBatteryLevel() {
|
|
203
|
+
this._options.enableBatteryLevel = true;
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
withoutBatteryLevel() {
|
|
208
|
+
this._options.enableBatteryLevel = false;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
withRetentionPeriodSeconds(retentionPeriodSeconds) {
|
|
213
|
+
this._options.retentionPeriodSeconds = retentionPeriodSeconds;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
withAdditionalChannels(additionalChannels) {
|
|
218
|
+
this._options.additionalChannels = [...(additionalChannels || [])];
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
addAdditionalChannel(additionalChannel) {
|
|
223
|
+
this._options.additionalChannels = [...(this._options.additionalChannels || []), additionalChannel];
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
withDefaultMemoryChannels(defaultMemoryChannels) {
|
|
228
|
+
this._options.defaultMemoryChannels = { ...(defaultMemoryChannels || {}) };
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
withoutDefaultMemoryChannels(defaultMemoryChannels) {
|
|
233
|
+
const current = {
|
|
234
|
+
managedHeap: true,
|
|
235
|
+
javaHeap: true,
|
|
236
|
+
nativeHeap: true,
|
|
237
|
+
residentSetSize: true,
|
|
238
|
+
rss: true,
|
|
239
|
+
physicalFootprint: true,
|
|
240
|
+
...(this._options.defaultMemoryChannels || {}),
|
|
241
|
+
};
|
|
242
|
+
Object.keys(defaultMemoryChannels || {}).forEach((key) => {
|
|
243
|
+
if (defaultMemoryChannels[key]) {
|
|
244
|
+
current[key] = false;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
this._options.defaultMemoryChannels = current;
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
withReactNativeMemoryProfiling(options = {}) {
|
|
252
|
+
this._options.reactNativeMemory = {
|
|
253
|
+
...(options || {}),
|
|
254
|
+
enabled: true,
|
|
255
|
+
};
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
withoutReactNativeMemoryProfiling() {
|
|
260
|
+
this._options.reactNativeMemory = false;
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
withSessionJpegCapture(optionsOrIntervalMilliseconds = {}) {
|
|
265
|
+
if (typeof optionsOrIntervalMilliseconds === "number") {
|
|
266
|
+
this._options.sessionJpegCapture = {
|
|
267
|
+
intervalMilliseconds: optionsOrIntervalMilliseconds,
|
|
268
|
+
quality: arguments.length > 1 ? arguments[1] : 60,
|
|
269
|
+
maxWidth: arguments.length > 2 ? arguments[2] : 480,
|
|
270
|
+
};
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
273
|
+
this._options.sessionJpegCapture = {
|
|
274
|
+
intervalMilliseconds: 2000,
|
|
275
|
+
quality: 60,
|
|
276
|
+
maxWidth: 480,
|
|
277
|
+
...(optionsOrIntervalMilliseconds || {}),
|
|
278
|
+
};
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
withoutSessionJpegCapture() {
|
|
283
|
+
this._options.sessionJpegCapture = false;
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
withTouchCapture(touchCapture = {}) {
|
|
288
|
+
this._options.touchCapture = { ...touchCapture };
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
withoutTouchCapture() {
|
|
293
|
+
this._options.touchCapture = false;
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
withLifecycleCapture(lifecycleCapture = {}) {
|
|
298
|
+
this._options.lifecycleCapture = { ...lifecycleCapture };
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
withToolGuard(toolGuard) {
|
|
303
|
+
this._options.toolGuard = toolGuard;
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
withToolsDisabled() {
|
|
308
|
+
return this.withToolGuard("disabled");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
withReadOnlyToolAccess() {
|
|
312
|
+
return this.withToolGuard("readOnly");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
withReadWriteToolAccess() {
|
|
316
|
+
return this.withToolGuard("readWrite");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
withAllToolAccess() {
|
|
320
|
+
return this.withToolGuard("fullAccess");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
registerCustomProperty(group, key, value) {
|
|
324
|
+
const normalizedGroup = String(group || "").trim();
|
|
325
|
+
const normalizedKey = String(key || "").trim();
|
|
326
|
+
if (!normalizedGroup || !normalizedKey) {
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
const customProperties = cloneOptions({ customProperties: this._options.customProperties }).customProperties || {};
|
|
330
|
+
customProperties[normalizedGroup] = {
|
|
331
|
+
...(customProperties[normalizedGroup] || {}),
|
|
332
|
+
[normalizedKey]: value == null ? "" : String(value).trim(),
|
|
333
|
+
};
|
|
334
|
+
this._options.customProperties = customProperties;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
removeCustomProperty(group, key) {
|
|
339
|
+
const normalizedGroup = String(group || "").trim();
|
|
340
|
+
const normalizedKey = String(key || "").trim();
|
|
341
|
+
const customProperties = cloneOptions({ customProperties: this._options.customProperties }).customProperties || {};
|
|
342
|
+
if (customProperties[normalizedGroup]) {
|
|
343
|
+
delete customProperties[normalizedGroup][normalizedKey];
|
|
344
|
+
if (Object.keys(customProperties[normalizedGroup]).length === 0) {
|
|
345
|
+
delete customProperties[normalizedGroup];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
this._options.customProperties = customProperties;
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
clearCustomProperties() {
|
|
353
|
+
this._options.customProperties = {};
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
withHostAutoProbe(hostAutoProbe = {}) {
|
|
358
|
+
this._options.hostAutoProbe = {
|
|
359
|
+
...hostAutoProbe,
|
|
360
|
+
enabled: true,
|
|
361
|
+
};
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
withoutHostAutoProbe() {
|
|
366
|
+
this._options.hostAutoProbe = {
|
|
367
|
+
...(this._options.hostAutoProbe || {}),
|
|
368
|
+
enabled: false,
|
|
369
|
+
};
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
withHostConnection(hostConnection = {}) {
|
|
374
|
+
this._options.hostConnection = { ...hostConnection };
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
configureHostConnection(configure) {
|
|
379
|
+
const hostConnection = { ...(this._options.hostConnection || {}) };
|
|
380
|
+
const configured = typeof configure === "function" ? configure(hostConnection) : hostConnection;
|
|
381
|
+
this._options.hostConnection = configured || hostConnection;
|
|
382
|
+
return this;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
withBundledHostConnection({ bundledDeveloperConfigJson = undefined, bundledConfigJson = undefined } = {}) {
|
|
386
|
+
return this.configureHostConnection((hostConnection) => ({
|
|
387
|
+
...hostConnection,
|
|
388
|
+
bundledDeveloperConfigJson,
|
|
389
|
+
bundledConfigJson,
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
withHostConnectionDiscoveryPort(discoveryPort) {
|
|
394
|
+
return this.configureHostConnection((hostConnection) => ({
|
|
395
|
+
...hostConnection,
|
|
396
|
+
discoveryPort,
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
withHostConnectionProfileRetentionSeconds(connectionProfileRetentionSeconds) {
|
|
401
|
+
return this.configureHostConnection((hostConnection) => ({
|
|
402
|
+
...hostConnection,
|
|
403
|
+
connectionProfileRetentionSeconds,
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
withSecureStorage(secureStorage = {}) {
|
|
408
|
+
this._options.secureStorage = {
|
|
409
|
+
...secureStorage,
|
|
410
|
+
allowedKeys: secureStorage.allowedKeys ? [...secureStorage.allowedKeys] : undefined,
|
|
411
|
+
allowedPrefixes: secureStorage.allowedPrefixes ? [...secureStorage.allowedPrefixes] : undefined,
|
|
412
|
+
};
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
withRemoteTools(remoteTools = {}) {
|
|
417
|
+
this._options.remoteTools = cloneRemoteToolsOptions(remoteTools);
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
withVisualTreeTools(options = {}) {
|
|
422
|
+
this._options.remoteTools = {
|
|
423
|
+
...(this._options.remoteTools || {}),
|
|
424
|
+
visualTree: typeof options === "boolean" ? options : {
|
|
425
|
+
...(options || {}),
|
|
426
|
+
enabled: !options || options.enabled !== false,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
withoutVisualTreeTools() {
|
|
433
|
+
this._options.remoteTools = {
|
|
434
|
+
...(this._options.remoteTools || {}),
|
|
435
|
+
visualTree: false,
|
|
436
|
+
};
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
withFileSystemTools(options = {}) {
|
|
441
|
+
this._options.remoteTools = {
|
|
442
|
+
...(this._options.remoteTools || {}),
|
|
443
|
+
fileSystem: {
|
|
444
|
+
...options,
|
|
445
|
+
additionalRoots: cloneRoots(options.additionalRoots),
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
withDatabaseTools(options = {}) {
|
|
452
|
+
this._options.remoteTools = {
|
|
453
|
+
...(this._options.remoteTools || {}),
|
|
454
|
+
database: {
|
|
455
|
+
...options,
|
|
456
|
+
additionalRoots: cloneRoots(options.additionalRoots),
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
return this;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
withPreferencesTools(options = {}) {
|
|
463
|
+
this._options.remoteTools = {
|
|
464
|
+
...(this._options.remoteTools || {}),
|
|
465
|
+
preferences: cloneRemoteToolsOptions({ preferences: options }).preferences,
|
|
466
|
+
};
|
|
467
|
+
return this;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
withReflectionTools(options = {}) {
|
|
471
|
+
this._options.remoteTools = {
|
|
472
|
+
...(this._options.remoteTools || {}),
|
|
473
|
+
reflection: cloneRemoteToolsOptions({ reflection: options }).reflection,
|
|
474
|
+
};
|
|
475
|
+
return this;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
build() {
|
|
479
|
+
return cloneOptions(this._options);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createOptionsBuilder(options = {}) {
|
|
484
|
+
return new AnsightOptionsBuilder(options);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function hostConnectionStatusSnapshot() {
|
|
488
|
+
const [status, capabilities] = await Promise.all([
|
|
489
|
+
nativeModule.hostConnectionStatus(),
|
|
490
|
+
nativeModule.hostConnectionCapabilities(),
|
|
491
|
+
]);
|
|
492
|
+
return { status, capabilities };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function hostConnectionStatusKey(snapshot) {
|
|
496
|
+
return JSON.stringify(snapshot || {});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function emitHostConnectionStatusChangedIfNeeded(force = false) {
|
|
500
|
+
if (hostConnectionStatusListeners.size === 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const snapshot = await hostConnectionStatusSnapshot();
|
|
506
|
+
const key = hostConnectionStatusKey(snapshot);
|
|
507
|
+
if (!force && key === lastHostConnectionStatusKey) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
lastHostConnectionStatusKey = key;
|
|
511
|
+
for (const listener of Array.from(hostConnectionStatusListeners)) {
|
|
512
|
+
listener(snapshot.status, snapshot.capabilities, snapshot);
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
// Status listeners should not change the result of the SDK operation that triggered them.
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function addHostConnectionStatusListener(listener, options = {}) {
|
|
520
|
+
if (typeof listener !== "function") {
|
|
521
|
+
throw new TypeError("addHostConnectionStatusListener requires a listener function.");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
hostConnectionStatusListeners.add(listener);
|
|
525
|
+
if (options.emitCurrent !== false) {
|
|
526
|
+
emitHostConnectionStatusChangedIfNeeded(true);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
remove() {
|
|
531
|
+
hostConnectionStatusListeners.delete(listener);
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function notifyAfterHostConnectionChange(operation) {
|
|
537
|
+
const result = await operation();
|
|
538
|
+
await emitHostConnectionStatusChangedIfNeeded();
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function normalizeLifecycleState(appState) {
|
|
543
|
+
if (appState === "active") {
|
|
544
|
+
return "foreground";
|
|
545
|
+
}
|
|
546
|
+
if (appState === "background" || appState === "inactive") {
|
|
547
|
+
return "background";
|
|
548
|
+
}
|
|
549
|
+
return "unknown";
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function ensureToolEventSubscription() {
|
|
553
|
+
if (toolEventSubscription) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const emitter = new NativeEventEmitter(nativeModule);
|
|
558
|
+
toolEventSubscription = emitter.addListener("AnsightToolCall", async (request) => {
|
|
559
|
+
const handler = toolHandlers.get(request.toolId);
|
|
560
|
+
if (!handler) {
|
|
561
|
+
await nativeModule.resolveToolCall(request.requestId, {
|
|
562
|
+
success: false,
|
|
563
|
+
message: `No JavaScript handler is registered for tool '${request.toolId}'.`,
|
|
564
|
+
errorCode: "javascript_tool_handler_missing",
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
const rawResult = await handler(request.arguments || {}, request);
|
|
571
|
+
const result = rawResult && typeof rawResult === "object"
|
|
572
|
+
? rawResult
|
|
573
|
+
: { success: true, result: rawResult };
|
|
574
|
+
|
|
575
|
+
await nativeModule.resolveToolCall(request.requestId, {
|
|
576
|
+
success: result.success !== false,
|
|
577
|
+
message: result.message,
|
|
578
|
+
errorCode: result.errorCode,
|
|
579
|
+
result: result.result,
|
|
580
|
+
});
|
|
581
|
+
} catch (error) {
|
|
582
|
+
await nativeModule.resolveToolCall(request.requestId, {
|
|
583
|
+
success: false,
|
|
584
|
+
message: error && error.message ? error.message : String(error),
|
|
585
|
+
errorCode: "javascript_tool_exception",
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function ensureLogEventSubscription() {
|
|
592
|
+
if (logEventSubscription) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const emitter = new NativeEventEmitter(nativeModule);
|
|
597
|
+
logEventSubscription = emitter.addListener("AnsightLog", (entry) => {
|
|
598
|
+
for (const listener of Array.from(logListeners)) {
|
|
599
|
+
try {
|
|
600
|
+
listener(entry || {});
|
|
601
|
+
} catch {
|
|
602
|
+
// Log listeners should not affect the SDK bridge or other listeners.
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function addLogListener(listener) {
|
|
609
|
+
if (typeof listener !== "function") {
|
|
610
|
+
throw new TypeError("addLogListener requires a listener function.");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
ensureLogEventSubscription();
|
|
614
|
+
logListeners.add(listener);
|
|
615
|
+
return {
|
|
616
|
+
remove() {
|
|
617
|
+
logListeners.delete(listener);
|
|
618
|
+
if (logListeners.size === 0 && logEventSubscription) {
|
|
619
|
+
logEventSubscription.remove();
|
|
620
|
+
logEventSubscription = null;
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function startAppStateTracking() {
|
|
627
|
+
if (appStateSubscription) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
nativeModule.setAppLifecycleState(normalizeLifecycleState(AppState.currentState));
|
|
632
|
+
appStateSubscription = AppState.addEventListener("change", (nextState) => {
|
|
633
|
+
nativeModule.setAppLifecycleState(normalizeLifecycleState(nextState));
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function stopAppStateTracking() {
|
|
638
|
+
if (appStateSubscription) {
|
|
639
|
+
appStateSubscription.remove();
|
|
640
|
+
appStateSubscription = null;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function parseBoolean(value, defaultValue = false) {
|
|
645
|
+
if (value == null || value === "") {
|
|
646
|
+
return defaultValue;
|
|
647
|
+
}
|
|
648
|
+
if (typeof value === "boolean") {
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
const normalized = String(value).trim().toLowerCase();
|
|
652
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") {
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
return defaultValue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function parseInteger(value, defaultValue, min, max) {
|
|
662
|
+
const parsed = Number.parseInt(value, 10);
|
|
663
|
+
const resolved = Number.isFinite(parsed) ? parsed : defaultValue;
|
|
664
|
+
return Math.min(max, Math.max(min, resolved));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const ARTIFACT_QUERY_TOOL_ID = "artifacts.query";
|
|
668
|
+
const ARTIFACT_REQUEST_TOOL_ID = "artifacts.request";
|
|
669
|
+
const FILE_TRANSFER_WIRE_PROTOCOL = "ansight.file-transfer.v1";
|
|
670
|
+
const ARTIFACT_REQUEST_RESERVED_KEYS = new Set([
|
|
671
|
+
"providerId",
|
|
672
|
+
"artifactId",
|
|
673
|
+
"downloadId",
|
|
674
|
+
"chunkBytes",
|
|
675
|
+
"arguments",
|
|
676
|
+
"__ansight_requestId",
|
|
677
|
+
"__ansight_sessionId",
|
|
678
|
+
]);
|
|
679
|
+
|
|
680
|
+
function trimString(value) {
|
|
681
|
+
const text = value == null ? "" : String(value).trim();
|
|
682
|
+
return text || null;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function normalizedTags(tags) {
|
|
686
|
+
const seen = new Set();
|
|
687
|
+
return (Array.isArray(tags) ? tags : [])
|
|
688
|
+
.map((tag) => trimString(tag))
|
|
689
|
+
.filter((tag) => {
|
|
690
|
+
if (!tag) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
const key = tag.toLowerCase();
|
|
694
|
+
if (seen.has(key)) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
seen.add(key);
|
|
698
|
+
return true;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function normalizedMetadata(metadata) {
|
|
703
|
+
const result = {};
|
|
704
|
+
Object.entries(metadata || {}).forEach(([key, value]) => {
|
|
705
|
+
const normalizedKey = trimString(key);
|
|
706
|
+
if (normalizedKey) {
|
|
707
|
+
result[normalizedKey] = value == null ? "" : String(value).trim();
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function artifactProviderDescriptor(provider) {
|
|
714
|
+
const descriptor = provider.descriptor || provider;
|
|
715
|
+
const id = trimString(descriptor.id);
|
|
716
|
+
const name = trimString(descriptor.name);
|
|
717
|
+
if (!id) {
|
|
718
|
+
throw new TypeError("Artifact provider id must not be blank.");
|
|
719
|
+
}
|
|
720
|
+
if (!name) {
|
|
721
|
+
throw new TypeError("Artifact provider name must not be blank.");
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
id,
|
|
725
|
+
name,
|
|
726
|
+
description: trimString(descriptor.description),
|
|
727
|
+
category: trimString(descriptor.category) || "app",
|
|
728
|
+
tags: normalizedTags(descriptor.tags),
|
|
729
|
+
metadata: normalizedMetadata(descriptor.metadata),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function artifactContentDescriptor(definition) {
|
|
734
|
+
const content = definition.content || {};
|
|
735
|
+
const supportedMimeTypes = content.supportedMimeTypes || definition.supportedMimeTypes || [definition.mimeType || content.defaultMimeType || "application/octet-stream"];
|
|
736
|
+
const normalizedMimeTypes = supportedMimeTypes.map((value) => trimString(value)).filter(Boolean);
|
|
737
|
+
const defaultMimeType = trimString(content.defaultMimeType || definition.mimeType) || normalizedMimeTypes[0] || "application/octet-stream";
|
|
738
|
+
return {
|
|
739
|
+
supportedMimeTypes: normalizedMimeTypes.length ? normalizedMimeTypes : [defaultMimeType],
|
|
740
|
+
defaultMimeType,
|
|
741
|
+
suggestedFileName: trimString(content.suggestedFileName || definition.fileName),
|
|
742
|
+
supportsText: !!content.supportsText,
|
|
743
|
+
supportsBinary: content.supportsBinary !== false,
|
|
744
|
+
sizeKnownBeforeCreation: !!content.sizeKnownBeforeCreation,
|
|
745
|
+
estimatedSizeBytes: content.estimatedSizeBytes ?? definition.estimatedSizeBytes ?? null,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function artifactDefinitionPayload(providerId, definition) {
|
|
750
|
+
const id = trimString(definition.id);
|
|
751
|
+
const name = trimString(definition.name);
|
|
752
|
+
const kind = trimString(definition.kind);
|
|
753
|
+
const category = trimString(definition.category);
|
|
754
|
+
if (!id || !name || !kind || !category) {
|
|
755
|
+
throw new TypeError("Artifact definitions require non-empty id, name, kind, and category.");
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
providerId,
|
|
759
|
+
id,
|
|
760
|
+
name,
|
|
761
|
+
description: trimString(definition.description) || "",
|
|
762
|
+
kind,
|
|
763
|
+
category,
|
|
764
|
+
tags: normalizedTags(definition.tags),
|
|
765
|
+
metadata: normalizedMetadata(definition.metadata),
|
|
766
|
+
content: artifactContentDescriptor(definition),
|
|
767
|
+
argumentsSchema: definition.argumentsSchema || { type: "object", additionalProperties: true },
|
|
768
|
+
security: definition.security || { level: "moderate", implications: ["metadata_disclosure"] },
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function utf8Bytes(text) {
|
|
773
|
+
if (typeof TextEncoder !== "undefined") {
|
|
774
|
+
return Array.from(new TextEncoder().encode(text));
|
|
775
|
+
}
|
|
776
|
+
const encoded = unescape(encodeURIComponent(text));
|
|
777
|
+
const bytes = new Array(encoded.length);
|
|
778
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
779
|
+
bytes[index] = encoded.charCodeAt(index);
|
|
780
|
+
}
|
|
781
|
+
return bytes;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function bytesFromValue(value) {
|
|
785
|
+
if (value == null) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
if (Array.isArray(value)) {
|
|
789
|
+
return value.map((byte) => Number(byte) & 0xff);
|
|
790
|
+
}
|
|
791
|
+
if (typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer) {
|
|
792
|
+
return Array.from(new Uint8Array(value));
|
|
793
|
+
}
|
|
794
|
+
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView && ArrayBuffer.isView(value)) {
|
|
795
|
+
return Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function base64FromBytes(bytes) {
|
|
801
|
+
if (typeof Buffer !== "undefined") {
|
|
802
|
+
return Buffer.from(bytes).toString("base64");
|
|
803
|
+
}
|
|
804
|
+
let output = "";
|
|
805
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
806
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
807
|
+
const first = bytes[index];
|
|
808
|
+
const second = index + 1 < bytes.length ? bytes[index + 1] : 0;
|
|
809
|
+
const third = index + 2 < bytes.length ? bytes[index + 2] : 0;
|
|
810
|
+
const triple = (first << 16) | (second << 8) | third;
|
|
811
|
+
output += alphabet[(triple >> 18) & 63];
|
|
812
|
+
output += alphabet[(triple >> 12) & 63];
|
|
813
|
+
output += index + 1 < bytes.length ? alphabet[(triple >> 6) & 63] : "=";
|
|
814
|
+
output += index + 2 < bytes.length ? alphabet[triple & 63] : "=";
|
|
815
|
+
}
|
|
816
|
+
return output;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function base64Size(base64) {
|
|
820
|
+
const normalized = String(base64 || "").replace(/\s/g, "");
|
|
821
|
+
if (!normalized) {
|
|
822
|
+
return 0;
|
|
823
|
+
}
|
|
824
|
+
const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
|
|
825
|
+
return Math.max(0, Math.floor((normalized.length * 3) / 4) - padding);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function normalizeArtifactPayload(result) {
|
|
829
|
+
const payload = result && typeof result === "object" && result.payload != null ? result.payload : result;
|
|
830
|
+
if (payload && typeof payload === "object") {
|
|
831
|
+
if (typeof payload.base64 === "string") {
|
|
832
|
+
return { base64: payload.base64, sizeBytes: payload.sizeBytes ?? base64Size(payload.base64) };
|
|
833
|
+
}
|
|
834
|
+
const bytes = bytesFromValue(payload.bytes || payload.data);
|
|
835
|
+
if (bytes) {
|
|
836
|
+
return { base64: base64FromBytes(bytes), sizeBytes: bytes.length };
|
|
837
|
+
}
|
|
838
|
+
if (typeof payload.text === "string") {
|
|
839
|
+
const bytesFromText = utf8Bytes(payload.text);
|
|
840
|
+
return { base64: base64FromBytes(bytesFromText), sizeBytes: bytesFromText.length };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (typeof payload === "string") {
|
|
844
|
+
const bytes = utf8Bytes(payload);
|
|
845
|
+
return { base64: base64FromBytes(bytes), sizeBytes: bytes.length };
|
|
846
|
+
}
|
|
847
|
+
const bytes = bytesFromValue(payload);
|
|
848
|
+
if (bytes) {
|
|
849
|
+
return { base64: base64FromBytes(bytes), sizeBytes: bytes.length };
|
|
850
|
+
}
|
|
851
|
+
throw new TypeError("Artifact result must include a payload as text, base64, bytes, ArrayBuffer, or Uint8Array.");
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function artifactRequestArguments(args = {}) {
|
|
855
|
+
const result = {};
|
|
856
|
+
Object.entries(args || {}).forEach(([key, value]) => {
|
|
857
|
+
if (!ARTIFACT_REQUEST_RESERVED_KEYS.has(key) && value != null) {
|
|
858
|
+
result[key] = String(value);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
const encoded = trimString(args.arguments);
|
|
862
|
+
if (!encoded) {
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
const nested = JSON.parse(encoded);
|
|
866
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) {
|
|
867
|
+
throw new TypeError("The artifact 'arguments' value must be a JSON object.");
|
|
868
|
+
}
|
|
869
|
+
Object.entries(nested).forEach(([key, value]) => {
|
|
870
|
+
result[key] = value == null
|
|
871
|
+
? ""
|
|
872
|
+
: typeof value === "object"
|
|
873
|
+
? JSON.stringify(value)
|
|
874
|
+
: String(value);
|
|
875
|
+
});
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function artifactMetadataPayload(rawMetadata, request, sizeBytes) {
|
|
880
|
+
const metadata = rawMetadata || {};
|
|
881
|
+
const artifactId = trimString(metadata.artifactId || request.artifactId);
|
|
882
|
+
const providerId = trimString(metadata.providerId || request.providerId);
|
|
883
|
+
const name = trimString(metadata.name);
|
|
884
|
+
const kind = trimString(metadata.kind);
|
|
885
|
+
const mimeType = trimString(metadata.mimeType);
|
|
886
|
+
const fileName = trimString(metadata.fileName);
|
|
887
|
+
if (!artifactId || artifactId.toLowerCase() !== request.artifactId.toLowerCase()) {
|
|
888
|
+
throw new TypeError("Artifact metadata artifactId must match the requested artifact id.");
|
|
889
|
+
}
|
|
890
|
+
if (!providerId || providerId.toLowerCase() !== request.providerId.toLowerCase()) {
|
|
891
|
+
throw new TypeError("Artifact metadata providerId must match the requested provider id.");
|
|
892
|
+
}
|
|
893
|
+
if (!name || !kind || !mimeType || !fileName) {
|
|
894
|
+
throw new TypeError("Artifact metadata requires non-empty name, kind, mimeType, and fileName.");
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
artifactId,
|
|
898
|
+
providerId,
|
|
899
|
+
name,
|
|
900
|
+
kind,
|
|
901
|
+
description: trimString(metadata.description),
|
|
902
|
+
mimeType,
|
|
903
|
+
fileName,
|
|
904
|
+
sizeBytes: metadata.sizeBytes == null ? sizeBytes : Number(metadata.sizeBytes),
|
|
905
|
+
createdAtUtc: trimString(metadata.createdAtUtc) || new Date().toISOString(),
|
|
906
|
+
tags: normalizedTags(metadata.tags),
|
|
907
|
+
metadata: normalizedMetadata(metadata.metadata),
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function artifactToolDefinitions() {
|
|
912
|
+
const genericObject = { type: "object", additionalProperties: true };
|
|
913
|
+
return [
|
|
914
|
+
{
|
|
915
|
+
id: ARTIFACT_QUERY_TOOL_ID,
|
|
916
|
+
name: "Query Artifacts",
|
|
917
|
+
description: "Queries app-provided artifact providers and currently requestable artifact definitions.",
|
|
918
|
+
category: "artifacts",
|
|
919
|
+
scope: "read",
|
|
920
|
+
keywords: ["artifact", "artifacts", "query", "catalog", "provider", "export", "snapshot"],
|
|
921
|
+
argumentsSchema: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
providerId: { type: ["string", "null"] },
|
|
925
|
+
category: { type: ["string", "null"] },
|
|
926
|
+
kind: { type: ["string", "null"] },
|
|
927
|
+
tag: { type: ["string", "null"] },
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
resultSchema: genericObject,
|
|
931
|
+
security: {
|
|
932
|
+
level: "moderate",
|
|
933
|
+
summary: "Discovers app-provided artifact definitions and descriptive metadata.",
|
|
934
|
+
implications: ["metadata_disclosure"],
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: ARTIFACT_REQUEST_TOOL_ID,
|
|
939
|
+
name: "Request Artifact",
|
|
940
|
+
description: "Requests an app-provided artifact snapshot and streams it to the host.",
|
|
941
|
+
category: "artifacts",
|
|
942
|
+
scope: "read",
|
|
943
|
+
keywords: ["artifact", "artifacts", "request", "export", "snapshot", "binary", "stream"],
|
|
944
|
+
argumentsSchema: {
|
|
945
|
+
type: "object",
|
|
946
|
+
properties: {
|
|
947
|
+
providerId: { type: "string" },
|
|
948
|
+
artifactId: { type: "string" },
|
|
949
|
+
downloadId: { type: ["string", "null"] },
|
|
950
|
+
chunkBytes: { type: "integer" },
|
|
951
|
+
arguments: genericObject,
|
|
952
|
+
},
|
|
953
|
+
required: ["providerId", "artifactId"],
|
|
954
|
+
},
|
|
955
|
+
resultSchema: genericObject,
|
|
956
|
+
security: {
|
|
957
|
+
level: "high",
|
|
958
|
+
summary: "Requests and exports an app-provided artifact snapshot.",
|
|
959
|
+
implications: ["exports_app_data", "binary_transfer"],
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
];
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async function handleArtifactQuery(args = {}, context = {}) {
|
|
966
|
+
const capturedAtUtc = new Date().toISOString();
|
|
967
|
+
const providerFilter = trimString(args.providerId);
|
|
968
|
+
const categoryFilter = trimString(args.category);
|
|
969
|
+
const kindFilter = trimString(args.kind);
|
|
970
|
+
const tagFilter = trimString(args.tag);
|
|
971
|
+
const providers = [];
|
|
972
|
+
const artifacts = [];
|
|
973
|
+
|
|
974
|
+
for (const provider of artifactProviders.values()) {
|
|
975
|
+
let descriptor;
|
|
976
|
+
try {
|
|
977
|
+
descriptor = artifactProviderDescriptor(provider);
|
|
978
|
+
if (providerFilter && descriptor.id.toLowerCase() !== providerFilter.toLowerCase()) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const query = typeof provider.query === "function" ? provider.query : provider.queryArtifacts;
|
|
982
|
+
const definitions = query
|
|
983
|
+
? await query.call(provider, {
|
|
984
|
+
toolRequestId: context.nativeRequestId || context.requestId,
|
|
985
|
+
sessionId: context.sessionId,
|
|
986
|
+
queriedAtUtc: capturedAtUtc,
|
|
987
|
+
})
|
|
988
|
+
: [];
|
|
989
|
+
providers.push({ ...descriptor, error: null });
|
|
990
|
+
(definitions || []).map((definition) => artifactDefinitionPayload(descriptor.id, definition))
|
|
991
|
+
.filter((definition) =>
|
|
992
|
+
(!categoryFilter || definition.category.toLowerCase() === categoryFilter.toLowerCase()) &&
|
|
993
|
+
(!kindFilter || definition.kind.toLowerCase() === kindFilter.toLowerCase()) &&
|
|
994
|
+
(!tagFilter || definition.tags.some((tag) => tag.toLowerCase() === tagFilter.toLowerCase()))
|
|
995
|
+
)
|
|
996
|
+
.forEach((definition) => artifacts.push(definition));
|
|
997
|
+
} catch (error) {
|
|
998
|
+
const fallback = descriptor || { id: "unknown", name: "Unknown", category: "app", tags: [], metadata: {} };
|
|
999
|
+
providers.push({ ...fallback, error: error && error.message ? error.message : String(error) });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return {
|
|
1004
|
+
success: true,
|
|
1005
|
+
result: {
|
|
1006
|
+
providers,
|
|
1007
|
+
artifacts,
|
|
1008
|
+
providerCount: providers.length,
|
|
1009
|
+
artifactCount: artifacts.length,
|
|
1010
|
+
capturedAtUtc,
|
|
1011
|
+
},
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async function handleArtifactRequest(args = {}, context = {}) {
|
|
1016
|
+
const providerId = trimString(args.providerId);
|
|
1017
|
+
const artifactId = trimString(args.artifactId);
|
|
1018
|
+
if (!providerId) {
|
|
1019
|
+
return { success: false, message: "Artifact request must include 'providerId'.", errorCode: "artifact_request_missing_provider_id" };
|
|
1020
|
+
}
|
|
1021
|
+
if (!artifactId) {
|
|
1022
|
+
return { success: false, message: "Artifact request must include 'artifactId'.", errorCode: "artifact_request_missing_artifact_id" };
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
let provider = null;
|
|
1026
|
+
for (const candidate of artifactProviders.values()) {
|
|
1027
|
+
if (artifactProviderDescriptor(candidate).id.toLowerCase() === providerId.toLowerCase()) {
|
|
1028
|
+
provider = candidate;
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (!provider) {
|
|
1033
|
+
return { success: false, message: `Artifact provider '${providerId}' is not registered.`, errorCode: "artifact_provider_not_found" };
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const nativeToolRequestId = trimString(context.nativeRequestId) || trimString(args.__ansight_requestId) || trimString(context.requestId);
|
|
1037
|
+
const bridgeRequestId = Platform.OS === "android" ? context.requestId : nativeToolRequestId;
|
|
1038
|
+
if (!bridgeRequestId) {
|
|
1039
|
+
return { success: false, message: "Artifact requests require a live tool protocol request context.", errorCode: "artifact_request_unavailable" };
|
|
1040
|
+
}
|
|
1041
|
+
if (typeof nativeModule.queueBinaryTransfer !== "function") {
|
|
1042
|
+
return { success: false, message: "Artifact binary transfer is not available in this native bridge.", errorCode: "artifact_transfer_unavailable" };
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const requestedAtUtc = new Date().toISOString();
|
|
1046
|
+
const request = {
|
|
1047
|
+
providerId,
|
|
1048
|
+
artifactId,
|
|
1049
|
+
arguments: artifactRequestArguments(args),
|
|
1050
|
+
context: {
|
|
1051
|
+
toolRequestId: nativeToolRequestId || bridgeRequestId,
|
|
1052
|
+
sessionId: context.sessionId || args.__ansight_sessionId,
|
|
1053
|
+
requestedAtUtc,
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1056
|
+
const create = typeof provider.create === "function" ? provider.create : provider.createArtifact;
|
|
1057
|
+
if (typeof create !== "function") {
|
|
1058
|
+
return { success: false, message: `Artifact provider '${providerId}' does not implement create().`, errorCode: "artifact_request_failed" };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const result = await create.call(provider, request);
|
|
1062
|
+
const payload = normalizeArtifactPayload(result);
|
|
1063
|
+
const metadata = artifactMetadataPayload(result && result.metadata, request, payload.sizeBytes);
|
|
1064
|
+
const chunkBytes = parseInteger(args.chunkBytes, 64 * 1024, 1024, 512 * 1024);
|
|
1065
|
+
const transfer = await nativeModule.queueBinaryTransfer(bridgeRequestId, payload.base64, chunkBytes);
|
|
1066
|
+
if (!transfer || transfer.success === false) {
|
|
1067
|
+
return {
|
|
1068
|
+
success: false,
|
|
1069
|
+
message: transfer && transfer.message ? transfer.message : "Artifact binary transfer could not be queued.",
|
|
1070
|
+
errorCode: transfer && transfer.errorCode ? transfer.errorCode : "artifact_transfer_unavailable",
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return {
|
|
1075
|
+
success: true,
|
|
1076
|
+
result: {
|
|
1077
|
+
artifact: metadata,
|
|
1078
|
+
downloadId: trimString(args.downloadId) || nativeToolRequestId || bridgeRequestId,
|
|
1079
|
+
transferId: transfer.transferId,
|
|
1080
|
+
deliveryMode: transfer.deliveryMode || "websocket_binary",
|
|
1081
|
+
wireProtocol: transfer.wireProtocol || FILE_TRANSFER_WIRE_PROTOCOL,
|
|
1082
|
+
status: transfer.status || "queued",
|
|
1083
|
+
chunkBytes: transfer.chunkBytes || chunkBytes,
|
|
1084
|
+
capturedAtUtc: requestedAtUtc,
|
|
1085
|
+
},
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function ensureArtifactToolsRegistered() {
|
|
1090
|
+
if (artifactToolRegistrations.length > 0) {
|
|
1091
|
+
return artifactToolRegistrations;
|
|
1092
|
+
}
|
|
1093
|
+
artifactToolRegistrations = artifactToolDefinitions().map((definition) =>
|
|
1094
|
+
registerTool(
|
|
1095
|
+
definition,
|
|
1096
|
+
definition.id === ARTIFACT_QUERY_TOOL_ID ? handleArtifactQuery : handleArtifactRequest
|
|
1097
|
+
)
|
|
1098
|
+
);
|
|
1099
|
+
return artifactToolRegistrations;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function registerArtifactProvider(provider) {
|
|
1103
|
+
if (!provider || typeof provider !== "object") {
|
|
1104
|
+
throw new TypeError("registerArtifactProvider requires a provider object.");
|
|
1105
|
+
}
|
|
1106
|
+
const descriptor = artifactProviderDescriptor(provider);
|
|
1107
|
+
artifactProviders.set(descriptor.id.toLowerCase(), provider);
|
|
1108
|
+
const registrations = ensureArtifactToolsRegistered();
|
|
1109
|
+
return {
|
|
1110
|
+
id: descriptor.id,
|
|
1111
|
+
ready: Promise.all(registrations.map((registration) => registration.ready)).then(() => ({
|
|
1112
|
+
success: true,
|
|
1113
|
+
message: `Registered artifact provider ${descriptor.id}.`,
|
|
1114
|
+
id: descriptor.id,
|
|
1115
|
+
})),
|
|
1116
|
+
unregister() {
|
|
1117
|
+
artifactProviders.delete(descriptor.id.toLowerCase());
|
|
1118
|
+
return Promise.resolve({
|
|
1119
|
+
success: true,
|
|
1120
|
+
message: `Unregistered artifact provider ${descriptor.id}.`,
|
|
1121
|
+
id: descriptor.id,
|
|
1122
|
+
});
|
|
1123
|
+
},
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function registerArtifactProviders(providers) {
|
|
1128
|
+
return (providers || []).map(registerArtifactProvider);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function unregisterArtifactProvider(providerId) {
|
|
1132
|
+
const id = trimString(providerId);
|
|
1133
|
+
if (!id) {
|
|
1134
|
+
return Promise.resolve({ success: false, message: "Artifact provider id must not be blank." });
|
|
1135
|
+
}
|
|
1136
|
+
const removed = artifactProviders.delete(id.toLowerCase());
|
|
1137
|
+
return Promise.resolve({
|
|
1138
|
+
success: removed,
|
|
1139
|
+
message: removed ? `Unregistered artifact provider ${id}.` : `Artifact provider ${id} is not registered.`,
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function listRegisteredArtifactProviders() {
|
|
1144
|
+
return Array.from(artifactProviders.values())
|
|
1145
|
+
.map((provider) => artifactProviderDescriptor(provider))
|
|
1146
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function clearArtifactProviders() {
|
|
1150
|
+
artifactProviders.clear();
|
|
1151
|
+
return Promise.resolve({ success: true, message: "Cleared artifact providers." });
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function getReactDevToolsHook() {
|
|
1155
|
+
return typeof global !== "undefined" ? global.__REACT_DEVTOOLS_GLOBAL_HOOK__ : undefined;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function rendererEntries(renderers) {
|
|
1159
|
+
if (!renderers) {
|
|
1160
|
+
return [];
|
|
1161
|
+
}
|
|
1162
|
+
if (typeof renderers.forEach === "function") {
|
|
1163
|
+
const entries = [];
|
|
1164
|
+
renderers.forEach((renderer, id) => entries.push([id, renderer]));
|
|
1165
|
+
return entries;
|
|
1166
|
+
}
|
|
1167
|
+
return Object.keys(renderers).map((id) => [id, renderers[id]]);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function rootsForRenderer(hook, rendererId) {
|
|
1171
|
+
if (hook && typeof hook.getFiberRoots === "function") {
|
|
1172
|
+
return Array.from(hook.getFiberRoots(rendererId) || []);
|
|
1173
|
+
}
|
|
1174
|
+
const fiberRoots = hook && hook._fiberRoots;
|
|
1175
|
+
if (fiberRoots && typeof fiberRoots.get === "function") {
|
|
1176
|
+
return Array.from(fiberRoots.get(rendererId) || []);
|
|
1177
|
+
}
|
|
1178
|
+
if (fiberRoots && fiberRoots[rendererId]) {
|
|
1179
|
+
return Array.from(fiberRoots[rendererId] || []);
|
|
1180
|
+
}
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function currentFiberRoot(root) {
|
|
1185
|
+
if (!root) {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
return root.current || root;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function getReactRoots() {
|
|
1192
|
+
const hook = getReactDevToolsHook();
|
|
1193
|
+
if (!hook) {
|
|
1194
|
+
return { hookAvailable: false, renderers: [], roots: [] };
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const renderers = rendererEntries(hook.renderers);
|
|
1198
|
+
const roots = [];
|
|
1199
|
+
renderers.forEach(([rendererId, renderer]) => {
|
|
1200
|
+
rootsForRenderer(hook, rendererId).forEach((root) => {
|
|
1201
|
+
const current = currentFiberRoot(root);
|
|
1202
|
+
if (current) {
|
|
1203
|
+
roots.push({ rendererId: String(rendererId), renderer, fiber: current });
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
return {
|
|
1209
|
+
hookAvailable: true,
|
|
1210
|
+
renderers: renderers.map(([id, renderer]) => ({
|
|
1211
|
+
id: String(id),
|
|
1212
|
+
packageName: renderer && renderer.rendererPackageName,
|
|
1213
|
+
version: renderer && renderer.version,
|
|
1214
|
+
bundleType: renderer && renderer.bundleType,
|
|
1215
|
+
})),
|
|
1216
|
+
roots,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function reactFiberId(fiber) {
|
|
1221
|
+
if (!fiber || (typeof fiber !== "object" && typeof fiber !== "function")) {
|
|
1222
|
+
return "react:unknown";
|
|
1223
|
+
}
|
|
1224
|
+
const existing = reactNodeIds.get(fiber);
|
|
1225
|
+
if (existing) {
|
|
1226
|
+
reactFiberById.set(existing, fiber);
|
|
1227
|
+
return existing;
|
|
1228
|
+
}
|
|
1229
|
+
const id = `react:${nextReactNodeId++}`;
|
|
1230
|
+
reactNodeIds.set(fiber, id);
|
|
1231
|
+
reactFiberById.set(id, fiber);
|
|
1232
|
+
return id;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function reactFiberKind(tag) {
|
|
1236
|
+
switch (tag) {
|
|
1237
|
+
case 0:
|
|
1238
|
+
return "function";
|
|
1239
|
+
case 1:
|
|
1240
|
+
return "class";
|
|
1241
|
+
case 3:
|
|
1242
|
+
return "root";
|
|
1243
|
+
case 5:
|
|
1244
|
+
return "host";
|
|
1245
|
+
case 6:
|
|
1246
|
+
return "text";
|
|
1247
|
+
case 7:
|
|
1248
|
+
return "fragment";
|
|
1249
|
+
case 9:
|
|
1250
|
+
return "contextConsumer";
|
|
1251
|
+
case 10:
|
|
1252
|
+
return "contextProvider";
|
|
1253
|
+
case 11:
|
|
1254
|
+
return "forwardRef";
|
|
1255
|
+
case 14:
|
|
1256
|
+
case 15:
|
|
1257
|
+
return "memo";
|
|
1258
|
+
case 19:
|
|
1259
|
+
return "suspenseList";
|
|
1260
|
+
case 22:
|
|
1261
|
+
return "offscreen";
|
|
1262
|
+
default:
|
|
1263
|
+
return `fiber:${tag}`;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function reactDisplayNameFromType(type) {
|
|
1268
|
+
if (!type) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
if (typeof type === "string") {
|
|
1272
|
+
return type;
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof type === "function") {
|
|
1275
|
+
return type.displayName || type.name || "Anonymous";
|
|
1276
|
+
}
|
|
1277
|
+
if (typeof type === "object") {
|
|
1278
|
+
if (type.displayName) {
|
|
1279
|
+
return type.displayName;
|
|
1280
|
+
}
|
|
1281
|
+
if (type.name) {
|
|
1282
|
+
return type.name;
|
|
1283
|
+
}
|
|
1284
|
+
if (type.render) {
|
|
1285
|
+
return `ForwardRef(${reactDisplayNameFromType(type.render) || "Anonymous"})`;
|
|
1286
|
+
}
|
|
1287
|
+
if (type.type) {
|
|
1288
|
+
return `Memo(${reactDisplayNameFromType(type.type) || "Anonymous"})`;
|
|
1289
|
+
}
|
|
1290
|
+
if (type._context && type._context.displayName) {
|
|
1291
|
+
return `${type._context.displayName}.Context`;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function reactFiberTypeName(fiber) {
|
|
1298
|
+
if (!fiber) {
|
|
1299
|
+
return "Unknown";
|
|
1300
|
+
}
|
|
1301
|
+
if (fiber.tag === 3) {
|
|
1302
|
+
return "ReactRoot";
|
|
1303
|
+
}
|
|
1304
|
+
if (fiber.tag === 6) {
|
|
1305
|
+
return "Text";
|
|
1306
|
+
}
|
|
1307
|
+
return reactDisplayNameFromType(fiber.elementType) ||
|
|
1308
|
+
reactDisplayNameFromType(fiber.type) ||
|
|
1309
|
+
reactFiberKind(fiber.tag);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function isSensitiveKey(key) {
|
|
1313
|
+
return /password|passwd|token|secret|authorization|cookie|credential|api[-_]?key|session/i.test(String(key || ""));
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function sanitizeString(value, maxLength) {
|
|
1317
|
+
if (value.length <= maxLength) {
|
|
1318
|
+
return value;
|
|
1319
|
+
}
|
|
1320
|
+
return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function summarizeReactChildren(children) {
|
|
1324
|
+
if (children == null || typeof children === "boolean") {
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
1328
|
+
return sanitizeString(String(children), 160);
|
|
1329
|
+
}
|
|
1330
|
+
if (Array.isArray(children)) {
|
|
1331
|
+
return `[${children.length} children]`;
|
|
1332
|
+
}
|
|
1333
|
+
if (typeof children === "object") {
|
|
1334
|
+
return "[ReactNode]";
|
|
1335
|
+
}
|
|
1336
|
+
return typeof children;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function sanitizeValue(value, options, depth = 0, key = "") {
|
|
1340
|
+
if (isSensitiveKey(key)) {
|
|
1341
|
+
return "[redacted]";
|
|
1342
|
+
}
|
|
1343
|
+
if (value == null || typeof value === "boolean" || typeof value === "number") {
|
|
1344
|
+
return value;
|
|
1345
|
+
}
|
|
1346
|
+
if (typeof value === "string") {
|
|
1347
|
+
return sanitizeString(value, options.maxStringLength);
|
|
1348
|
+
}
|
|
1349
|
+
if (typeof value === "function") {
|
|
1350
|
+
return `[Function${value.name ? ` ${value.name}` : ""}]`;
|
|
1351
|
+
}
|
|
1352
|
+
if (typeof value === "symbol") {
|
|
1353
|
+
return value.toString();
|
|
1354
|
+
}
|
|
1355
|
+
if (depth >= options.maxValueDepth) {
|
|
1356
|
+
if (Array.isArray(value)) {
|
|
1357
|
+
return `[Array(${value.length})]`;
|
|
1358
|
+
}
|
|
1359
|
+
return `[${value.constructor && value.constructor.name ? value.constructor.name : "Object"}]`;
|
|
1360
|
+
}
|
|
1361
|
+
if (Array.isArray(value)) {
|
|
1362
|
+
return value.slice(0, options.maxArrayLength).map((entry) => sanitizeValue(entry, options, depth + 1));
|
|
1363
|
+
}
|
|
1364
|
+
if (typeof value === "object") {
|
|
1365
|
+
if (value.$$typeof) {
|
|
1366
|
+
return "[ReactElement]";
|
|
1367
|
+
}
|
|
1368
|
+
const result = {};
|
|
1369
|
+
Object.keys(value).slice(0, options.maxObjectKeys).forEach((entryKey) => {
|
|
1370
|
+
result[entryKey] = entryKey === "children"
|
|
1371
|
+
? summarizeReactChildren(value[entryKey])
|
|
1372
|
+
: sanitizeValue(value[entryKey], options, depth + 1, entryKey);
|
|
1373
|
+
});
|
|
1374
|
+
return result;
|
|
1375
|
+
}
|
|
1376
|
+
return String(value);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function summarizeProps(props) {
|
|
1380
|
+
if (!props || typeof props !== "object") {
|
|
1381
|
+
return {};
|
|
1382
|
+
}
|
|
1383
|
+
const keys = Object.keys(props).filter((key) => key !== "children");
|
|
1384
|
+
const summary = { keys };
|
|
1385
|
+
["testID", "nativeID", "accessibilityLabel", "role"].forEach((key) => {
|
|
1386
|
+
if (props[key] != null && !isSensitiveKey(key)) {
|
|
1387
|
+
summary[key] = String(props[key]);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
const children = summarizeReactChildren(props.children);
|
|
1391
|
+
if (children != null) {
|
|
1392
|
+
summary.children = children;
|
|
1393
|
+
}
|
|
1394
|
+
return summary;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function nativeTagForFiber(fiber) {
|
|
1398
|
+
const stateNode = fiber && fiber.stateNode;
|
|
1399
|
+
if (!stateNode) {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
if (typeof stateNode === "number") {
|
|
1403
|
+
return stateNode;
|
|
1404
|
+
}
|
|
1405
|
+
if (stateNode._nativeTag != null) {
|
|
1406
|
+
return stateNode._nativeTag;
|
|
1407
|
+
}
|
|
1408
|
+
if (stateNode.canonical && stateNode.canonical.nativeTag != null) {
|
|
1409
|
+
return stateNode.canonical.nativeTag;
|
|
1410
|
+
}
|
|
1411
|
+
if (typeof findNodeHandle === "function") {
|
|
1412
|
+
try {
|
|
1413
|
+
return findNodeHandle(stateNode);
|
|
1414
|
+
} catch (_) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function measureNativeTag(nativeTag, timeoutMilliseconds = 250) {
|
|
1422
|
+
return new Promise((resolve) => {
|
|
1423
|
+
if (!nativeTag || !UIManager || typeof UIManager.measureInWindow !== "function") {
|
|
1424
|
+
resolve(null);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
let settled = false;
|
|
1428
|
+
const timeout = setTimeout(() => {
|
|
1429
|
+
if (!settled) {
|
|
1430
|
+
settled = true;
|
|
1431
|
+
resolve(null);
|
|
1432
|
+
}
|
|
1433
|
+
}, timeoutMilliseconds);
|
|
1434
|
+
try {
|
|
1435
|
+
UIManager.measureInWindow(nativeTag, (x, y, width, height) => {
|
|
1436
|
+
if (settled) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
settled = true;
|
|
1440
|
+
clearTimeout(timeout);
|
|
1441
|
+
resolve({
|
|
1442
|
+
x,
|
|
1443
|
+
y,
|
|
1444
|
+
width,
|
|
1445
|
+
height,
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
} catch (_) {
|
|
1449
|
+
if (!settled) {
|
|
1450
|
+
settled = true;
|
|
1451
|
+
clearTimeout(timeout);
|
|
1452
|
+
resolve(null);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function serializeFiber(fiber, context, depth) {
|
|
1459
|
+
if (!fiber || context.count >= context.maxNodes || context.visited.has(fiber)) {
|
|
1460
|
+
context.truncated = true;
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
context.visited.add(fiber);
|
|
1464
|
+
context.count += 1;
|
|
1465
|
+
|
|
1466
|
+
const props = fiber.memoizedProps || fiber.pendingProps || {};
|
|
1467
|
+
const type = reactFiberTypeName(fiber);
|
|
1468
|
+
const kind = reactFiberKind(fiber.tag);
|
|
1469
|
+
const node = {
|
|
1470
|
+
id: reactFiberId(fiber),
|
|
1471
|
+
type,
|
|
1472
|
+
kind,
|
|
1473
|
+
tag: fiber.tag,
|
|
1474
|
+
key: fiber.key == null ? null : String(fiber.key),
|
|
1475
|
+
depth,
|
|
1476
|
+
children: [],
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
if (fiber._debugSource) {
|
|
1480
|
+
node.source = {
|
|
1481
|
+
fileName: fiber._debugSource.fileName,
|
|
1482
|
+
lineNumber: fiber._debugSource.lineNumber,
|
|
1483
|
+
columnNumber: fiber._debugSource.columnNumber,
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const owner = fiber._debugOwner && reactFiberTypeName(fiber._debugOwner);
|
|
1488
|
+
if (owner) {
|
|
1489
|
+
node.owner = owner;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const nativeTag = nativeTagForFiber(fiber);
|
|
1493
|
+
if (nativeTag != null) {
|
|
1494
|
+
node.nativeTag = nativeTag;
|
|
1495
|
+
context.nodesWithNativeTags.push(node);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
if (fiber.tag === 6) {
|
|
1499
|
+
node.label = sanitizeString(String(fiber.memoizedProps || fiber.pendingProps || ""), context.maxStringLength);
|
|
1500
|
+
} else if (props && typeof props === "object") {
|
|
1501
|
+
const propSummary = summarizeProps(props);
|
|
1502
|
+
if (Object.keys(propSummary).length > 0) {
|
|
1503
|
+
node.propsSummary = propSummary;
|
|
1504
|
+
if (propSummary.testID || propSummary.nativeID) {
|
|
1505
|
+
node.automationId = propSummary.testID || propSummary.nativeID;
|
|
1506
|
+
}
|
|
1507
|
+
if (!node.label && (propSummary.accessibilityLabel || typeof propSummary.children === "string")) {
|
|
1508
|
+
node.label = propSummary.accessibilityLabel || propSummary.children;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (context.includeProps) {
|
|
1512
|
+
node.props = sanitizeValue(props, context);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
if (context.includeState && fiber.memoizedState != null) {
|
|
1517
|
+
node.state = sanitizeValue(fiber.memoizedState, context);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
if (depth < context.maxDepth) {
|
|
1521
|
+
let child = fiber.child;
|
|
1522
|
+
while (child) {
|
|
1523
|
+
const childNode = serializeFiber(child, context, depth + 1);
|
|
1524
|
+
if (childNode) {
|
|
1525
|
+
node.children.push(childNode);
|
|
1526
|
+
}
|
|
1527
|
+
child = child.sibling;
|
|
1528
|
+
}
|
|
1529
|
+
} else if (fiber.child) {
|
|
1530
|
+
context.truncated = true;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
node.childCount = node.children.length;
|
|
1534
|
+
return node;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function isShadowTreeFiber(fiber) {
|
|
1538
|
+
return fiber && (fiber.tag === 3 || fiber.tag === 5 || fiber.tag === 6);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function createShadowTreeNode(fiber, context, depth) {
|
|
1542
|
+
const props = fiber.memoizedProps || fiber.pendingProps || {};
|
|
1543
|
+
const node = {
|
|
1544
|
+
id: reactFiberId(fiber),
|
|
1545
|
+
type: reactFiberTypeName(fiber),
|
|
1546
|
+
kind: fiber.tag === 3 ? "root" : fiber.tag === 6 ? "text" : "host",
|
|
1547
|
+
tag: fiber.tag,
|
|
1548
|
+
key: fiber.key == null ? null : String(fiber.key),
|
|
1549
|
+
depth,
|
|
1550
|
+
children: [],
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
const nativeTag = nativeTagForFiber(fiber);
|
|
1554
|
+
if (nativeTag != null) {
|
|
1555
|
+
node.nativeTag = nativeTag;
|
|
1556
|
+
context.nodesWithNativeTags.push(node);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
if (fiber.tag === 6) {
|
|
1560
|
+
node.label = sanitizeString(String(fiber.memoizedProps || fiber.pendingProps || ""), context.maxStringLength);
|
|
1561
|
+
} else if (props && typeof props === "object") {
|
|
1562
|
+
const propSummary = summarizeProps(props);
|
|
1563
|
+
if (Object.keys(propSummary).length > 0) {
|
|
1564
|
+
node.propsSummary = propSummary;
|
|
1565
|
+
if (propSummary.testID || propSummary.nativeID) {
|
|
1566
|
+
node.automationId = propSummary.testID || propSummary.nativeID;
|
|
1567
|
+
}
|
|
1568
|
+
if (!node.label && (propSummary.accessibilityLabel || typeof propSummary.children === "string")) {
|
|
1569
|
+
node.label = propSummary.accessibilityLabel || propSummary.children;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (context.includeProps) {
|
|
1573
|
+
node.props = sanitizeValue(props, context);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return node;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
function serializeShadowFiber(fiber, context, depth) {
|
|
1581
|
+
if (!fiber || context.visited.has(fiber)) {
|
|
1582
|
+
return [];
|
|
1583
|
+
}
|
|
1584
|
+
if (context.count >= context.maxNodes) {
|
|
1585
|
+
context.truncated = true;
|
|
1586
|
+
return [];
|
|
1587
|
+
}
|
|
1588
|
+
context.visited.add(fiber);
|
|
1589
|
+
|
|
1590
|
+
const materialize = isShadowTreeFiber(fiber);
|
|
1591
|
+
const nodeDepth = materialize ? depth : depth - 1;
|
|
1592
|
+
let node = null;
|
|
1593
|
+
if (materialize) {
|
|
1594
|
+
context.count += 1;
|
|
1595
|
+
node = createShadowTreeNode(fiber, context, nodeDepth);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const childNodes = [];
|
|
1599
|
+
const childDepth = materialize ? nodeDepth + 1 : depth;
|
|
1600
|
+
if (!materialize || nodeDepth < context.maxDepth) {
|
|
1601
|
+
let child = fiber.child;
|
|
1602
|
+
while (child) {
|
|
1603
|
+
const serializedChildren = serializeShadowFiber(child, context, childDepth);
|
|
1604
|
+
serializedChildren.forEach((childNode) => childNodes.push(childNode));
|
|
1605
|
+
child = child.sibling;
|
|
1606
|
+
}
|
|
1607
|
+
} else if (fiber.child) {
|
|
1608
|
+
context.truncated = true;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (!node) {
|
|
1612
|
+
return childNodes;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
node.children = childNodes;
|
|
1616
|
+
node.childCount = node.children.length;
|
|
1617
|
+
return [node];
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
async function captureReactVisualTree(rawOptions = {}) {
|
|
1621
|
+
reactFiberById = new Map();
|
|
1622
|
+
const includeBounds = rawOptions.includeBounds !== false;
|
|
1623
|
+
const context = {
|
|
1624
|
+
includeProps: !!rawOptions.includeProps,
|
|
1625
|
+
includeState: !!rawOptions.includeState,
|
|
1626
|
+
maxArrayLength: rawOptions.maxArrayLength || 12,
|
|
1627
|
+
maxDepth: rawOptions.maxDepth || 30,
|
|
1628
|
+
maxNodes: rawOptions.maxNodes || 1500,
|
|
1629
|
+
maxObjectKeys: rawOptions.maxObjectKeys || 40,
|
|
1630
|
+
maxStringLength: rawOptions.maxStringLength || 180,
|
|
1631
|
+
maxValueDepth: rawOptions.maxValueDepth || 2,
|
|
1632
|
+
nodesWithNativeTags: [],
|
|
1633
|
+
count: 0,
|
|
1634
|
+
truncated: false,
|
|
1635
|
+
visited: new Set(),
|
|
1636
|
+
};
|
|
1637
|
+
const roots = getReactRoots();
|
|
1638
|
+
const rootNodes = roots.roots.map((root, index) => {
|
|
1639
|
+
const node = serializeFiber(root.fiber, context, 0);
|
|
1640
|
+
if (node) {
|
|
1641
|
+
node.rendererId = root.rendererId;
|
|
1642
|
+
node.rootIndex = index;
|
|
1643
|
+
}
|
|
1644
|
+
return node;
|
|
1645
|
+
}).filter(Boolean);
|
|
1646
|
+
|
|
1647
|
+
if (includeBounds && context.nodesWithNativeTags.length > 0) {
|
|
1648
|
+
await Promise.all(context.nodesWithNativeTags.slice(0, 300).map(async (node) => {
|
|
1649
|
+
const bounds = await measureNativeTag(node.nativeTag);
|
|
1650
|
+
if (bounds && bounds.width > 0 && bounds.height > 0) {
|
|
1651
|
+
node.bounds = bounds;
|
|
1652
|
+
}
|
|
1653
|
+
}));
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
return {
|
|
1657
|
+
platform: Platform.OS,
|
|
1658
|
+
source: "react",
|
|
1659
|
+
adapter: "react.fiber",
|
|
1660
|
+
treeKind: "component",
|
|
1661
|
+
capturedAtUtc: new Date().toISOString(),
|
|
1662
|
+
hookAvailable: roots.hookAvailable,
|
|
1663
|
+
renderers: roots.renderers,
|
|
1664
|
+
root: {
|
|
1665
|
+
id: "react:roots",
|
|
1666
|
+
type: "ReactRoots",
|
|
1667
|
+
kind: "container",
|
|
1668
|
+
children: rootNodes,
|
|
1669
|
+
},
|
|
1670
|
+
roots: rootNodes,
|
|
1671
|
+
nodeCount: context.count,
|
|
1672
|
+
truncated: context.truncated,
|
|
1673
|
+
unavailableReason: roots.hookAvailable ? undefined : "React DevTools global hook is not available in this runtime.",
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
async function captureReactShadowTree(rawOptions = {}) {
|
|
1678
|
+
reactFiberById = new Map();
|
|
1679
|
+
const includeBounds = rawOptions.includeBounds !== false;
|
|
1680
|
+
const context = {
|
|
1681
|
+
includeProps: !!rawOptions.includeProps,
|
|
1682
|
+
includeState: false,
|
|
1683
|
+
maxArrayLength: rawOptions.maxArrayLength || 8,
|
|
1684
|
+
maxDepth: rawOptions.maxDepth || 30,
|
|
1685
|
+
maxNodes: rawOptions.maxNodes || 1500,
|
|
1686
|
+
maxObjectKeys: rawOptions.maxObjectKeys || 24,
|
|
1687
|
+
maxStringLength: rawOptions.maxStringLength || 180,
|
|
1688
|
+
maxValueDepth: rawOptions.maxValueDepth || 2,
|
|
1689
|
+
nodesWithNativeTags: [],
|
|
1690
|
+
count: 0,
|
|
1691
|
+
truncated: false,
|
|
1692
|
+
visited: new Set(),
|
|
1693
|
+
};
|
|
1694
|
+
const roots = getReactRoots();
|
|
1695
|
+
const rootNodes = roots.roots.flatMap((root, index) => {
|
|
1696
|
+
const nodes = serializeShadowFiber(root.fiber, context, 0);
|
|
1697
|
+
nodes.forEach((node) => {
|
|
1698
|
+
node.rendererId = root.rendererId;
|
|
1699
|
+
node.rootIndex = index;
|
|
1700
|
+
});
|
|
1701
|
+
return nodes;
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
if (includeBounds && context.nodesWithNativeTags.length > 0) {
|
|
1705
|
+
await Promise.all(context.nodesWithNativeTags.slice(0, 300).map(async (node) => {
|
|
1706
|
+
const bounds = await measureNativeTag(node.nativeTag);
|
|
1707
|
+
if (bounds && bounds.width > 0 && bounds.height > 0) {
|
|
1708
|
+
node.bounds = bounds;
|
|
1709
|
+
}
|
|
1710
|
+
}));
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
return {
|
|
1714
|
+
platform: Platform.OS,
|
|
1715
|
+
source: "react-native",
|
|
1716
|
+
adapter: "react-native.host-fiber",
|
|
1717
|
+
treeKind: "shadow",
|
|
1718
|
+
capturedAtUtc: new Date().toISOString(),
|
|
1719
|
+
hookAvailable: roots.hookAvailable,
|
|
1720
|
+
renderers: roots.renderers,
|
|
1721
|
+
root: {
|
|
1722
|
+
id: "react:shadow-roots",
|
|
1723
|
+
type: "ReactNativeShadowRoots",
|
|
1724
|
+
kind: "container",
|
|
1725
|
+
children: rootNodes,
|
|
1726
|
+
},
|
|
1727
|
+
roots: rootNodes,
|
|
1728
|
+
nodeCount: context.count,
|
|
1729
|
+
truncated: context.truncated,
|
|
1730
|
+
unavailableReason: roots.hookAvailable ? undefined : "React DevTools global hook is not available in this runtime.",
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function flattenReactTree(node, output = []) {
|
|
1735
|
+
if (!node) {
|
|
1736
|
+
return output;
|
|
1737
|
+
}
|
|
1738
|
+
output.push(node);
|
|
1739
|
+
(node.children || []).forEach((child) => flattenReactTree(child, output));
|
|
1740
|
+
return output;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function nodeSearchText(node) {
|
|
1744
|
+
return [
|
|
1745
|
+
node.id,
|
|
1746
|
+
node.type,
|
|
1747
|
+
node.kind,
|
|
1748
|
+
node.label,
|
|
1749
|
+
node.owner,
|
|
1750
|
+
node.propsSummary && node.propsSummary.testID,
|
|
1751
|
+
node.propsSummary && node.propsSummary.nativeID,
|
|
1752
|
+
node.propsSummary && node.propsSummary.accessibilityLabel,
|
|
1753
|
+
node.propsSummary && node.propsSummary.children,
|
|
1754
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function matchesReactNode(node, args) {
|
|
1758
|
+
const query = args.query ? String(args.query).toLowerCase() : null;
|
|
1759
|
+
const type = args.type ? String(args.type).toLowerCase() : null;
|
|
1760
|
+
const testID = args.testID ? String(args.testID).toLowerCase() : null;
|
|
1761
|
+
const text = args.text ? String(args.text).toLowerCase() : null;
|
|
1762
|
+
const searchText = nodeSearchText(node);
|
|
1763
|
+
return (!query || searchText.includes(query)) &&
|
|
1764
|
+
(!type || String(node.type || "").toLowerCase().includes(type)) &&
|
|
1765
|
+
(!testID || String((node.propsSummary && node.propsSummary.testID) || "").toLowerCase() === testID) &&
|
|
1766
|
+
(!text || searchText.includes(text));
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
function currentNavigationObject() {
|
|
1770
|
+
return reactToolNavigationRef && reactToolNavigationRef.current
|
|
1771
|
+
? reactToolNavigationRef.current
|
|
1772
|
+
: reactToolNavigationRef;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function navigationStateSnapshot() {
|
|
1776
|
+
const navigation = currentNavigationObject();
|
|
1777
|
+
if (!navigation) {
|
|
1778
|
+
return {
|
|
1779
|
+
available: false,
|
|
1780
|
+
message: "No React Navigation ref was provided to installReactTools.",
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
const state = typeof navigation.getRootState === "function"
|
|
1784
|
+
? navigation.getRootState()
|
|
1785
|
+
: typeof navigation.getState === "function"
|
|
1786
|
+
? navigation.getState()
|
|
1787
|
+
: null;
|
|
1788
|
+
const currentRoute = typeof navigation.getCurrentRoute === "function"
|
|
1789
|
+
? navigation.getCurrentRoute()
|
|
1790
|
+
: null;
|
|
1791
|
+
return {
|
|
1792
|
+
available: true,
|
|
1793
|
+
currentRoute: currentRoute ? sanitizeValue(currentRoute, { maxArrayLength: 10, maxObjectKeys: 30, maxStringLength: 160, maxValueDepth: 2 }) : null,
|
|
1794
|
+
state: state ? sanitizeValue(state, { maxArrayLength: 30, maxObjectKeys: 80, maxStringLength: 160, maxValueDepth: 4 }) : null,
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
function reactToolOptions(baseOptions, args) {
|
|
1799
|
+
return {
|
|
1800
|
+
includeBounds: parseBoolean(args.includeBounds, baseOptions.includeBounds !== false),
|
|
1801
|
+
includeProps: parseBoolean(args.includeProps, !!baseOptions.includeProps),
|
|
1802
|
+
includeState: parseBoolean(args.includeState, !!baseOptions.includeState),
|
|
1803
|
+
maxDepth: parseInteger(args.maxDepth, baseOptions.maxDepth || 30, 1, 120),
|
|
1804
|
+
maxNodes: parseInteger(args.maxNodes, baseOptions.maxNodes || 1500, 1, 10000),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
function reactToolDefinitions(enableActions) {
|
|
1809
|
+
const tools = [
|
|
1810
|
+
{
|
|
1811
|
+
id: REACT_COMPONENT_TREE_TOOL_ID,
|
|
1812
|
+
name: "Get React Component Tree",
|
|
1813
|
+
description: "Captures the live React Fiber component tree for the current React Native runtime.",
|
|
1814
|
+
category: "react",
|
|
1815
|
+
scope: "read",
|
|
1816
|
+
keywords: ["react", "react-native", "fiber", "component", "component-tree"],
|
|
1817
|
+
argumentsSchema: {
|
|
1818
|
+
type: "object",
|
|
1819
|
+
properties: {
|
|
1820
|
+
maxDepth: { type: "integer" },
|
|
1821
|
+
maxNodes: { type: "integer" },
|
|
1822
|
+
includeBounds: { type: "boolean" },
|
|
1823
|
+
includeProps: { type: "boolean" },
|
|
1824
|
+
includeState: { type: "boolean" },
|
|
1825
|
+
},
|
|
1826
|
+
},
|
|
1827
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1828
|
+
security: {
|
|
1829
|
+
level: "high",
|
|
1830
|
+
summary: "Inspects the React component tree and optionally sanitized props/state.",
|
|
1831
|
+
implications: ["inspects_runtime_state", "metadata_disclosure"],
|
|
1832
|
+
},
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
id: REACT_SHADOW_TREE_TOOL_ID,
|
|
1836
|
+
name: "Get React Shadow Tree",
|
|
1837
|
+
description: "Captures the committed React Native host tree with composite components flattened out.",
|
|
1838
|
+
category: "react",
|
|
1839
|
+
scope: "read",
|
|
1840
|
+
keywords: ["react", "react-native", "host", "shadow", "shadow-tree", "layout"],
|
|
1841
|
+
argumentsSchema: {
|
|
1842
|
+
type: "object",
|
|
1843
|
+
properties: {
|
|
1844
|
+
maxDepth: { type: "integer" },
|
|
1845
|
+
maxNodes: { type: "integer" },
|
|
1846
|
+
includeBounds: { type: "boolean" },
|
|
1847
|
+
includeProps: { type: "boolean" },
|
|
1848
|
+
},
|
|
1849
|
+
},
|
|
1850
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1851
|
+
security: {
|
|
1852
|
+
level: "high",
|
|
1853
|
+
summary: "Inspects the React Native host tree and optionally sanitized host props.",
|
|
1854
|
+
implications: ["inspects_runtime_state", "metadata_disclosure"],
|
|
1855
|
+
},
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
id: "react.find_components",
|
|
1859
|
+
name: "Find React Components",
|
|
1860
|
+
description: "Searches the live React component tree by component type, text, testID, nativeID, or accessibility label.",
|
|
1861
|
+
category: "react",
|
|
1862
|
+
scope: "read",
|
|
1863
|
+
keywords: ["react", "search", "component", "testid", "accessibility"],
|
|
1864
|
+
argumentsSchema: {
|
|
1865
|
+
type: "object",
|
|
1866
|
+
properties: {
|
|
1867
|
+
query: { type: "string" },
|
|
1868
|
+
type: { type: "string" },
|
|
1869
|
+
testID: { type: "string" },
|
|
1870
|
+
text: { type: "string" },
|
|
1871
|
+
maxResults: { type: "integer" },
|
|
1872
|
+
includeProps: { type: "boolean" },
|
|
1873
|
+
},
|
|
1874
|
+
},
|
|
1875
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1876
|
+
security: {
|
|
1877
|
+
level: "high",
|
|
1878
|
+
summary: "Searches React runtime metadata and may reveal UI text.",
|
|
1879
|
+
implications: ["inspects_runtime_state", "metadata_disclosure"],
|
|
1880
|
+
},
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
id: "react.get_component",
|
|
1884
|
+
name: "Get React Component",
|
|
1885
|
+
description: "Returns a single React component node from the current React visual tree.",
|
|
1886
|
+
category: "react",
|
|
1887
|
+
scope: "read",
|
|
1888
|
+
keywords: ["react", "component", "props", "fiber"],
|
|
1889
|
+
argumentsSchema: {
|
|
1890
|
+
type: "object",
|
|
1891
|
+
properties: {
|
|
1892
|
+
nodeId: { type: "string" },
|
|
1893
|
+
includeProps: { type: "boolean" },
|
|
1894
|
+
includeState: { type: "boolean" },
|
|
1895
|
+
includeBounds: { type: "boolean" },
|
|
1896
|
+
},
|
|
1897
|
+
required: ["nodeId"],
|
|
1898
|
+
},
|
|
1899
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1900
|
+
security: {
|
|
1901
|
+
level: "high",
|
|
1902
|
+
summary: "Reads one React component node and optionally sanitized props/state.",
|
|
1903
|
+
implications: ["inspects_runtime_state", "metadata_disclosure"],
|
|
1904
|
+
},
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
id: "react.get_navigation_state",
|
|
1908
|
+
name: "Get React Navigation State",
|
|
1909
|
+
description: "Returns the configured React Navigation ref state when installReactTools receives a navigationRef.",
|
|
1910
|
+
category: "react",
|
|
1911
|
+
scope: "read",
|
|
1912
|
+
keywords: ["react", "navigation", "route", "screen"],
|
|
1913
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1914
|
+
security: {
|
|
1915
|
+
level: "medium",
|
|
1916
|
+
summary: "Reads navigation route metadata from a configured navigation ref.",
|
|
1917
|
+
implications: ["inspects_runtime_state", "metadata_disclosure"],
|
|
1918
|
+
},
|
|
1919
|
+
},
|
|
1920
|
+
];
|
|
1921
|
+
|
|
1922
|
+
if (enableActions) {
|
|
1923
|
+
tools.push({
|
|
1924
|
+
id: "react.invoke_component_action",
|
|
1925
|
+
name: "Invoke React Component Action",
|
|
1926
|
+
description: "Invokes an allow-listed function prop on a React component, such as onPress.",
|
|
1927
|
+
category: "react",
|
|
1928
|
+
scope: "write",
|
|
1929
|
+
keywords: ["react", "component", "action", "onpress"],
|
|
1930
|
+
argumentsSchema: {
|
|
1931
|
+
type: "object",
|
|
1932
|
+
properties: {
|
|
1933
|
+
nodeId: { type: "string" },
|
|
1934
|
+
prop: { type: "string" },
|
|
1935
|
+
},
|
|
1936
|
+
required: ["nodeId", "prop"],
|
|
1937
|
+
},
|
|
1938
|
+
resultSchema: { type: "object", additionalProperties: true },
|
|
1939
|
+
security: {
|
|
1940
|
+
level: "critical",
|
|
1941
|
+
summary: "Invokes app code through a React prop function.",
|
|
1942
|
+
implications: ["invokes_app_code", "mutates_runtime_state"],
|
|
1943
|
+
},
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
return tools;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
function installReactTools(options = {}) {
|
|
1951
|
+
uninstallReactTools();
|
|
1952
|
+
reactToolNavigationRef = options.navigationRef || null;
|
|
1953
|
+
const enableActions = !!options.enableActions;
|
|
1954
|
+
const allowedActionProps = new Set(options.allowedActionProps || ["onPress", "onClick", "onSubmitEditing"]);
|
|
1955
|
+
const registrations = [];
|
|
1956
|
+
|
|
1957
|
+
reactToolDefinitions(enableActions).forEach((definition) => {
|
|
1958
|
+
const registration = registerTool(definition, async (args = {}) => {
|
|
1959
|
+
if (definition.id === REACT_COMPONENT_TREE_TOOL_ID) {
|
|
1960
|
+
const tree = await captureReactVisualTree(reactToolOptions(options, args));
|
|
1961
|
+
return {
|
|
1962
|
+
success: tree.hookAvailable && tree.nodeCount > 0,
|
|
1963
|
+
message: tree.hookAvailable ? "React component tree captured." : tree.unavailableReason,
|
|
1964
|
+
result: tree,
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
if (definition.id === REACT_SHADOW_TREE_TOOL_ID) {
|
|
1969
|
+
const tree = await captureReactShadowTree(reactToolOptions(options, args));
|
|
1970
|
+
return {
|
|
1971
|
+
success: tree.hookAvailable && tree.nodeCount > 0,
|
|
1972
|
+
message: tree.hookAvailable ? "React shadow tree captured." : tree.unavailableReason,
|
|
1973
|
+
result: tree,
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
if (definition.id === "react.find_components") {
|
|
1978
|
+
const tree = await captureReactVisualTree({
|
|
1979
|
+
...reactToolOptions(options, args),
|
|
1980
|
+
includeBounds: false,
|
|
1981
|
+
includeProps: parseBoolean(args.includeProps, !!options.includeProps),
|
|
1982
|
+
});
|
|
1983
|
+
const maxResults = parseInteger(args.maxResults, 50, 1, 500);
|
|
1984
|
+
const matches = flattenReactTree(tree.root)
|
|
1985
|
+
.filter((node) => node.id !== "react:roots" && matchesReactNode(node, args))
|
|
1986
|
+
.slice(0, maxResults);
|
|
1987
|
+
return {
|
|
1988
|
+
success: tree.hookAvailable,
|
|
1989
|
+
message: `Found ${matches.length} React component(s).`,
|
|
1990
|
+
result: {
|
|
1991
|
+
matches,
|
|
1992
|
+
count: matches.length,
|
|
1993
|
+
truncated: matches.length === maxResults,
|
|
1994
|
+
},
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (definition.id === "react.get_component") {
|
|
1999
|
+
const tree = await captureReactVisualTree(reactToolOptions(options, args));
|
|
2000
|
+
const node = flattenReactTree(tree.root).find((candidate) => candidate.id === args.nodeId);
|
|
2001
|
+
return node
|
|
2002
|
+
? { success: true, message: "React component captured.", result: node }
|
|
2003
|
+
: { success: false, message: `React component '${args.nodeId}' was not found.`, errorCode: "react_component_not_found" };
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
if (definition.id === "react.get_navigation_state") {
|
|
2007
|
+
return {
|
|
2008
|
+
success: true,
|
|
2009
|
+
message: "React navigation state captured.",
|
|
2010
|
+
result: navigationStateSnapshot(),
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
if (definition.id === "react.invoke_component_action") {
|
|
2015
|
+
if (!enableActions) {
|
|
2016
|
+
return { success: false, message: "React component actions are disabled.", errorCode: "react_actions_disabled" };
|
|
2017
|
+
}
|
|
2018
|
+
const prop = String(args.prop || "");
|
|
2019
|
+
if (!allowedActionProps.has(prop)) {
|
|
2020
|
+
return { success: false, message: `React action prop '${prop}' is not allowed.`, errorCode: "react_action_not_allowed" };
|
|
2021
|
+
}
|
|
2022
|
+
const tree = await captureReactVisualTree({ ...reactToolOptions(options, args), includeBounds: false });
|
|
2023
|
+
const node = flattenReactTree(tree.root).find((candidate) => candidate.id === args.nodeId);
|
|
2024
|
+
if (!node) {
|
|
2025
|
+
return { success: false, message: `React component '${args.nodeId}' was not found.`, errorCode: "react_component_not_found" };
|
|
2026
|
+
}
|
|
2027
|
+
const fiber = reactFiberById.get(args.nodeId);
|
|
2028
|
+
const action = fiber && fiber.memoizedProps && fiber.memoizedProps[prop];
|
|
2029
|
+
if (typeof action !== "function") {
|
|
2030
|
+
return {
|
|
2031
|
+
success: false,
|
|
2032
|
+
message: `React component '${args.nodeId}' does not expose a function prop named '${prop}'.`,
|
|
2033
|
+
errorCode: "react_action_not_found",
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
await action();
|
|
2037
|
+
return {
|
|
2038
|
+
success: true,
|
|
2039
|
+
message: `Invoked React component action '${prop}'.`,
|
|
2040
|
+
result: {
|
|
2041
|
+
nodeId: args.nodeId,
|
|
2042
|
+
prop,
|
|
2043
|
+
type: node.type,
|
|
2044
|
+
},
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
return { success: false, message: `Unsupported React tool '${definition.id}'.`, errorCode: "react_tool_unknown" };
|
|
2049
|
+
});
|
|
2050
|
+
registrations.push(registration);
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
reactToolRegistrations = registrations;
|
|
2054
|
+
return {
|
|
2055
|
+
ids: registrations.map((registration) => registration.id),
|
|
2056
|
+
ready: Promise.all(registrations.map((registration) => registration.ready)),
|
|
2057
|
+
unregister: uninstallReactTools,
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
function uninstallReactTools() {
|
|
2062
|
+
const existing = reactToolRegistrations;
|
|
2063
|
+
reactToolRegistrations = [];
|
|
2064
|
+
reactToolNavigationRef = null;
|
|
2065
|
+
const pending = existing.map((registration) => {
|
|
2066
|
+
toolHandlers.delete(registration.id);
|
|
2067
|
+
return nativeModule.unregisterCustomTool(registration.id).catch(() => {});
|
|
2068
|
+
});
|
|
2069
|
+
return Promise.all(pending);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function installErrorHandlers(options = {}) {
|
|
2073
|
+
const previousHandler = global.ErrorUtils && global.ErrorUtils.getGlobalHandler
|
|
2074
|
+
? global.ErrorUtils.getGlobalHandler()
|
|
2075
|
+
: undefined;
|
|
2076
|
+
|
|
2077
|
+
if (global.ErrorUtils && global.ErrorUtils.setGlobalHandler) {
|
|
2078
|
+
global.ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
2079
|
+
nativeModule.recordEvent({
|
|
2080
|
+
label: error && error.message ? error.message : "Unhandled JavaScript error",
|
|
2081
|
+
type: "Exception",
|
|
2082
|
+
details: JSON.stringify({
|
|
2083
|
+
fatal: !!isFatal,
|
|
2084
|
+
name: error && error.name,
|
|
2085
|
+
message: error && error.message,
|
|
2086
|
+
stack: error && error.stack,
|
|
2087
|
+
}),
|
|
2088
|
+
}).catch(() => {});
|
|
2089
|
+
|
|
2090
|
+
if (typeof previousHandler === "function" && options.chain !== false) {
|
|
2091
|
+
previousHandler(error, isFatal);
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
const previousRejectionTracking = global.__ansightUnhandledRejectionTrackingInstalled;
|
|
2097
|
+
if (!previousRejectionTracking && typeof global.addEventListener === "function") {
|
|
2098
|
+
global.__ansightUnhandledRejectionTrackingInstalled = true;
|
|
2099
|
+
global.addEventListener("unhandledrejection", (event) => {
|
|
2100
|
+
const reason = event && event.reason;
|
|
2101
|
+
nativeModule.recordEvent({
|
|
2102
|
+
label: reason && reason.message ? reason.message : "Unhandled JavaScript promise rejection",
|
|
2103
|
+
type: "Exception",
|
|
2104
|
+
details: JSON.stringify({
|
|
2105
|
+
name: reason && reason.name,
|
|
2106
|
+
message: reason && reason.message,
|
|
2107
|
+
stack: reason && reason.stack,
|
|
2108
|
+
}),
|
|
2109
|
+
}).catch(() => {});
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
return () => {
|
|
2114
|
+
if (global.ErrorUtils && global.ErrorUtils.setGlobalHandler && previousHandler) {
|
|
2115
|
+
global.ErrorUtils.setGlobalHandler(previousHandler);
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
async function initialize(options = {}) {
|
|
2121
|
+
const result = await nativeModule.initialize(normalizeOptions(options));
|
|
2122
|
+
if (options.lifecycle !== false) {
|
|
2123
|
+
startAppStateTracking();
|
|
2124
|
+
}
|
|
2125
|
+
await emitHostConnectionStatusChangedIfNeeded();
|
|
2126
|
+
return result;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
async function initializeAndActivate(options = {}) {
|
|
2130
|
+
const result = await nativeModule.initializeAndActivate(normalizeOptions(options));
|
|
2131
|
+
if (options.lifecycle !== false) {
|
|
2132
|
+
startAppStateTracking();
|
|
2133
|
+
}
|
|
2134
|
+
await emitHostConnectionStatusChangedIfNeeded();
|
|
2135
|
+
return result;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
async function deactivate() {
|
|
2139
|
+
stopAppStateTracking();
|
|
2140
|
+
return notifyAfterHostConnectionChange(() => nativeModule.deactivate());
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
function registerTool(definition, handler) {
|
|
2144
|
+
if (!definition || typeof definition !== "object") {
|
|
2145
|
+
throw new TypeError("registerTool requires a tool definition object.");
|
|
2146
|
+
}
|
|
2147
|
+
if (!definition.id || typeof definition.id !== "string") {
|
|
2148
|
+
throw new TypeError("registerTool requires a stable string id.");
|
|
2149
|
+
}
|
|
2150
|
+
if (typeof handler !== "function") {
|
|
2151
|
+
throw new TypeError("registerTool requires a JavaScript handler function.");
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
ensureToolEventSubscription();
|
|
2155
|
+
toolHandlers.set(definition.id, handler);
|
|
2156
|
+
const registration = nativeModule.registerCustomTool(definition);
|
|
2157
|
+
return {
|
|
2158
|
+
id: definition.id,
|
|
2159
|
+
ready: registration,
|
|
2160
|
+
unregister() {
|
|
2161
|
+
toolHandlers.delete(definition.id);
|
|
2162
|
+
return nativeModule.unregisterCustomTool(definition.id);
|
|
2163
|
+
},
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
function unregisterTool(id) {
|
|
2168
|
+
toolHandlers.delete(id);
|
|
2169
|
+
return nativeModule.unregisterCustomTool(id);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function connect(pairingPayload, options = {}) {
|
|
2173
|
+
return notifyAfterHostConnectionChange(() => nativeModule.connect(normalizePairingPayload(pairingPayload), options));
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
function openSession(pairingPayload, options = {}) {
|
|
2177
|
+
return notifyAfterHostConnectionChange(() => nativeModule.openSession(normalizePairingPayload(pairingPayload), options));
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
function recordEvent(input) {
|
|
2181
|
+
if (typeof input === "string") {
|
|
2182
|
+
return nativeModule.recordEvent({ label: input });
|
|
2183
|
+
}
|
|
2184
|
+
return nativeModule.recordEvent(input || {});
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
function screenViewed(name, details) {
|
|
2188
|
+
return nativeModule.screenViewed(name, details || {});
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
function trackRoute(name, details) {
|
|
2192
|
+
return screenViewed(name, details);
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function createReactNavigationTracker(navigationRef, options = {}) {
|
|
2196
|
+
let currentRouteName;
|
|
2197
|
+
return {
|
|
2198
|
+
onReady() {
|
|
2199
|
+
currentRouteName = navigationRef && navigationRef.getCurrentRoute
|
|
2200
|
+
? navigationRef.getCurrentRoute()?.name
|
|
2201
|
+
: undefined;
|
|
2202
|
+
if (currentRouteName && options.recordInitial !== false) {
|
|
2203
|
+
screenViewed(currentRouteName, { source: "react-navigation" }).catch(() => {});
|
|
2204
|
+
}
|
|
2205
|
+
},
|
|
2206
|
+
onStateChange() {
|
|
2207
|
+
const nextRouteName = navigationRef && navigationRef.getCurrentRoute
|
|
2208
|
+
? navigationRef.getCurrentRoute()?.name
|
|
2209
|
+
: undefined;
|
|
2210
|
+
if (nextRouteName && nextRouteName !== currentRouteName) {
|
|
2211
|
+
currentRouteName = nextRouteName;
|
|
2212
|
+
screenViewed(nextRouteName, { source: "react-navigation" }).catch(() => {});
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
const Ansight = {
|
|
2219
|
+
initialize,
|
|
2220
|
+
initializeAndActivate,
|
|
2221
|
+
activate: () => notifyAfterHostConnectionChange(() => nativeModule.activate()),
|
|
2222
|
+
deactivate,
|
|
2223
|
+
clear: () => notifyAfterHostConnectionChange(() => nativeModule.clear()),
|
|
2224
|
+
registerMetricChannel: (channel) => nativeModule.registerMetricChannel(channel),
|
|
2225
|
+
metric: (value, channel = 255) => nativeModule.recordMetric(value, channel),
|
|
2226
|
+
recordMetric: (value, channel = 255) => nativeModule.recordMetric(value, channel),
|
|
2227
|
+
event: recordEvent,
|
|
2228
|
+
recordEvent,
|
|
2229
|
+
screenViewed,
|
|
2230
|
+
trackRoute,
|
|
2231
|
+
setAppLifecycleState: (state) => nativeModule.setAppLifecycleState(state),
|
|
2232
|
+
connect,
|
|
2233
|
+
openSession,
|
|
2234
|
+
disconnect: () => notifyAfterHostConnectionChange(() => nativeModule.disconnect()),
|
|
2235
|
+
completeSession: () => notifyAfterHostConnectionChange(() => nativeModule.completeSession()),
|
|
2236
|
+
closeSession: () => notifyAfterHostConnectionChange(() => nativeModule.closeSession()),
|
|
2237
|
+
savePairingConfig: (pairingPayload, options = {}) =>
|
|
2238
|
+
notifyAfterHostConnectionChange(() => nativeModule.savePairingConfig(normalizePairingPayload(pairingPayload), options)),
|
|
2239
|
+
clearSavedPairing: () => notifyAfterHostConnectionChange(() => nativeModule.clearSavedPairing()),
|
|
2240
|
+
clearSavedPairingConfig: () => notifyAfterHostConnectionChange(() => nativeModule.clearSavedPairing()),
|
|
2241
|
+
clearCachedSession: () => notifyAfterHostConnectionChange(() => nativeModule.clearCachedSession()),
|
|
2242
|
+
notifyHostConnectionConfigChanged: () =>
|
|
2243
|
+
notifyAfterHostConnectionChange(() => nativeModule.notifyHostConnectionConfigChanged()),
|
|
2244
|
+
status: () => nativeModule.status(),
|
|
2245
|
+
snapshot: () => nativeModule.snapshot(),
|
|
2246
|
+
hostConnectionStatus: () => nativeModule.hostConnectionStatus(),
|
|
2247
|
+
hostConnectionCapabilities: () => nativeModule.hostConnectionCapabilities(),
|
|
2248
|
+
addHostConnectionStatusListener,
|
|
2249
|
+
currentOptions: () => nativeModule.currentOptions(),
|
|
2250
|
+
recordedMetrics: (limit = 0) => nativeModule.recordedMetrics(limit || 0),
|
|
2251
|
+
recordedEvents: (limit = 0) => nativeModule.recordedEvents(limit || 0),
|
|
2252
|
+
sendClientLog: (line) => nativeModule.sendClientLog(line),
|
|
2253
|
+
addLogListener,
|
|
2254
|
+
captureBuiltInTelemetrySample: () => nativeModule.captureBuiltInTelemetrySample(),
|
|
2255
|
+
isFramesPerSecondEnabled: () => nativeModule.isFramesPerSecondEnabled(),
|
|
2256
|
+
enableFramesPerSecond: () => nativeModule.enableFramesPerSecond(),
|
|
2257
|
+
disableFramesPerSecond: () => nativeModule.disableFramesPerSecond(),
|
|
2258
|
+
captureScreenFrame: (options = {}) => nativeModule.captureScreenFrame(options || {}),
|
|
2259
|
+
enableTouchCapture: () => nativeModule.enableTouchCapture(),
|
|
2260
|
+
disableTouchCapture: () => nativeModule.disableTouchCapture(),
|
|
2261
|
+
updateSessionProperties: (properties) => nativeModule.updateSessionProperties(properties || {}),
|
|
2262
|
+
clearSessionProperties: () => nativeModule.clearSessionProperties(),
|
|
2263
|
+
updateCustomProperties: (properties) => nativeModule.updateSessionProperties(properties || {}),
|
|
2264
|
+
registerCustomProperty: (group, key, value) => nativeModule.registerCustomProperty(group, key, value),
|
|
2265
|
+
removeCustomProperty: (group, key) => nativeModule.removeCustomProperty(group, key),
|
|
2266
|
+
clearCustomProperties: () => nativeModule.clearSessionProperties(),
|
|
2267
|
+
registerTool,
|
|
2268
|
+
unregisterTool,
|
|
2269
|
+
registerArtifactProvider,
|
|
2270
|
+
registerArtifactProviders,
|
|
2271
|
+
unregisterArtifactProvider,
|
|
2272
|
+
listRegisteredArtifactProviders,
|
|
2273
|
+
clearArtifactProviders,
|
|
2274
|
+
listRegisteredTools: () => Array.from(toolHandlers.keys()),
|
|
2275
|
+
clearRegisteredTools: () => {
|
|
2276
|
+
toolHandlers.clear();
|
|
2277
|
+
artifactProviders.clear();
|
|
2278
|
+
artifactToolRegistrations = [];
|
|
2279
|
+
return nativeModule.clearRegisteredCustomTools();
|
|
2280
|
+
},
|
|
2281
|
+
createOptionsBuilder,
|
|
2282
|
+
AnsightOptionsBuilder,
|
|
2283
|
+
startAppStateTracking,
|
|
2284
|
+
stopAppStateTracking,
|
|
2285
|
+
installReactTools,
|
|
2286
|
+
uninstallReactTools,
|
|
2287
|
+
installErrorHandlers,
|
|
2288
|
+
createReactNavigationTracker,
|
|
2289
|
+
platform: Platform.OS,
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2292
|
+
module.exports = Ansight;
|
|
2293
|
+
module.exports.default = Ansight;
|
|
2294
|
+
module.exports.AnsightOptionsBuilder = AnsightOptionsBuilder;
|
|
2295
|
+
module.exports.createOptionsBuilder = createOptionsBuilder;
|
|
2296
|
+
module.exports.notifyHostConnectionConfigChanged = Ansight.notifyHostConnectionConfigChanged;
|
|
2297
|
+
module.exports.addHostConnectionStatusListener = addHostConnectionStatusListener;
|
|
2298
|
+
module.exports.addLogListener = addLogListener;
|
|
2299
|
+
module.exports.registerArtifactProvider = registerArtifactProvider;
|
|
2300
|
+
module.exports.registerArtifactProviders = registerArtifactProviders;
|
|
2301
|
+
module.exports.unregisterArtifactProvider = unregisterArtifactProvider;
|
|
2302
|
+
module.exports.listRegisteredArtifactProviders = listRegisteredArtifactProviders;
|
|
2303
|
+
module.exports.clearArtifactProviders = clearArtifactProviders;
|