react-native-inapp-inspector 2.3.15 → 2.3.16

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.
Files changed (96) hide show
  1. package/README.md +1 -0
  2. package/dist/commonjs/components/Inspector/AnalyticsTab.js +0 -22
  3. package/dist/commonjs/components/Inspector/ConsoleTab.js +0 -22
  4. package/dist/commonjs/components/Inspector/CrashTab.js +0 -23
  5. package/dist/commonjs/components/Inspector/FeedbackModal.d.ts +7 -0
  6. package/dist/commonjs/components/Inspector/FeedbackModal.js +552 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +56 -4
  8. package/dist/commonjs/components/Inspector/MainScreen.js +45 -3
  9. package/dist/commonjs/components/Inspector/NetworkTab.js +27 -38
  10. package/dist/commonjs/components/Inspector/NpmStarPrompt.d.ts +3 -0
  11. package/dist/commonjs/components/Inspector/NpmStarPrompt.js +494 -0
  12. package/dist/commonjs/components/Inspector/PerformanceTab.js +8 -1
  13. package/dist/commonjs/components/Inspector/ReduxTab.js +0 -22
  14. package/dist/commonjs/components/Inspector/SettingsPanel.js +1035 -173
  15. package/dist/commonjs/components/Inspector/StorageTab.js +0 -23
  16. package/dist/commonjs/components/Inspector/TabBar.js +50 -15
  17. package/dist/commonjs/components/NetworkIcons.d.ts +5 -0
  18. package/dist/commonjs/components/NetworkIcons.js +33 -2
  19. package/dist/commonjs/constants/version.d.ts +1 -1
  20. package/dist/commonjs/constants/version.js +1 -1
  21. package/dist/commonjs/helpers/index.d.ts +1 -0
  22. package/dist/commonjs/helpers/index.js +1 -0
  23. package/dist/commonjs/helpers/remoteConfig.d.ts +4 -0
  24. package/dist/commonjs/helpers/remoteConfig.js +165 -0
  25. package/dist/commonjs/helpers/searchQueryParser.js +2 -0
  26. package/dist/commonjs/helpers/telemetry.d.ts +5 -0
  27. package/dist/commonjs/helpers/telemetry.js +339 -8
  28. package/dist/commonjs/index.d.ts +1 -0
  29. package/dist/commonjs/index.js +32 -2
  30. package/dist/commonjs/styles/AppFonts.d.ts +2 -0
  31. package/dist/commonjs/styles/AppFonts.js +1 -0
  32. package/dist/commonjs/styles/index.d.ts +22 -0
  33. package/dist/commonjs/styles/index.js +19 -0
  34. package/dist/commonjs/types/enums.d.ts +1 -0
  35. package/dist/commonjs/types/enums.js +1 -0
  36. package/dist/commonjs/types/interfaces.d.ts +12 -0
  37. package/dist/esm/components/Inspector/AnalyticsTab.js +1 -23
  38. package/dist/esm/components/Inspector/ConsoleTab.js +1 -23
  39. package/dist/esm/components/Inspector/CrashTab.js +1 -24
  40. package/dist/esm/components/Inspector/FeedbackModal.d.ts +7 -0
  41. package/dist/esm/components/Inspector/FeedbackModal.js +515 -0
  42. package/dist/esm/components/Inspector/InspectorHeader.js +57 -5
  43. package/dist/esm/components/Inspector/MainScreen.js +46 -4
  44. package/dist/esm/components/Inspector/NetworkTab.js +28 -39
  45. package/dist/esm/components/Inspector/NpmStarPrompt.d.ts +3 -0
  46. package/dist/esm/components/Inspector/NpmStarPrompt.js +454 -0
  47. package/dist/esm/components/Inspector/PerformanceTab.js +8 -1
  48. package/dist/esm/components/Inspector/ReduxTab.js +1 -23
  49. package/dist/esm/components/Inspector/SettingsPanel.js +1038 -176
  50. package/dist/esm/components/Inspector/StorageTab.js +0 -23
  51. package/dist/esm/components/Inspector/TabBar.js +50 -15
  52. package/dist/esm/components/NetworkIcons.d.ts +5 -0
  53. package/dist/esm/components/NetworkIcons.js +26 -0
  54. package/dist/esm/constants/version.d.ts +1 -1
  55. package/dist/esm/constants/version.js +1 -1
  56. package/dist/esm/helpers/index.d.ts +1 -0
  57. package/dist/esm/helpers/index.js +1 -0
  58. package/dist/esm/helpers/remoteConfig.d.ts +4 -0
  59. package/dist/esm/helpers/remoteConfig.js +160 -0
  60. package/dist/esm/helpers/searchQueryParser.js +2 -0
  61. package/dist/esm/helpers/telemetry.d.ts +5 -0
  62. package/dist/esm/helpers/telemetry.js +332 -9
  63. package/dist/esm/index.d.ts +1 -0
  64. package/dist/esm/index.js +30 -2
  65. package/dist/esm/styles/AppFonts.d.ts +2 -0
  66. package/dist/esm/styles/AppFonts.js +1 -0
  67. package/dist/esm/styles/index.d.ts +22 -0
  68. package/dist/esm/styles/index.js +19 -0
  69. package/dist/esm/types/enums.d.ts +1 -0
  70. package/dist/esm/types/enums.js +1 -0
  71. package/dist/esm/types/interfaces.d.ts +12 -0
  72. package/package.json +1 -1
  73. package/src/components/Inspector/AnalyticsTab.tsx +0 -23
  74. package/src/components/Inspector/ConsoleTab.tsx +0 -23
  75. package/src/components/Inspector/CrashTab.tsx +0 -23
  76. package/src/components/Inspector/FeedbackModal.tsx +626 -0
  77. package/src/components/Inspector/InspectorHeader.tsx +78 -2
  78. package/src/components/Inspector/MainScreen.tsx +59 -3
  79. package/src/components/Inspector/NetworkTab.tsx +69 -78
  80. package/src/components/Inspector/NpmStarPrompt.tsx +532 -0
  81. package/src/components/Inspector/PerformanceTab.tsx +8 -1
  82. package/src/components/Inspector/ReduxTab.tsx +0 -23
  83. package/src/components/Inspector/SettingsPanel.tsx +1162 -197
  84. package/src/components/Inspector/StorageTab.tsx +0 -23
  85. package/src/components/Inspector/TabBar.tsx +57 -20
  86. package/src/components/NetworkIcons.tsx +92 -0
  87. package/src/constants/version.ts +1 -1
  88. package/src/helpers/index.ts +1 -0
  89. package/src/helpers/remoteConfig.ts +212 -0
  90. package/src/helpers/searchQueryParser.ts +3 -0
  91. package/src/helpers/telemetry.ts +381 -10
  92. package/src/index.tsx +42 -0
  93. package/src/styles/AppFonts.ts +2 -0
  94. package/src/styles/index.ts +20 -1
  95. package/src/types/enums.ts +1 -0
  96. package/src/types/interfaces.ts +12 -0
@@ -1,6 +1,18 @@
1
- import {Platform} from 'react-native';
1
+ import React from 'react';
2
+ import {Appearance, Dimensions, PixelRatio, Platform, NativeModules} from 'react-native';
2
3
  import {LIB_VERSION} from '../constants/version';
3
- import {loadSettings, saveSettings} from './settingsStore';
4
+ import {loadSettings, saveSettings, PersistedSettings} from './settingsStore';
5
+ import {isReduxConnected} from '../customHooks/reduxLogger';
6
+ import {isFirebaseAnalyticsPatched} from '../customHooks/analyticsLogger';
7
+ import {getCrashRecords} from '../customHooks/crashHandler';
8
+ import {getNetworkLogs} from '../customHooks/networkLogger';
9
+ import {isNativeModuleAvailable} from '../native/NativeInspector';
10
+ import {
11
+ getAppName,
12
+ getBundleIdentifier,
13
+ getAppVersionAndBuild,
14
+ isLocalDebugEnvironment,
15
+ } from './index';
4
16
  import {
5
17
  ENC_MID,
6
18
  ENC_SEC,
@@ -77,21 +89,36 @@ function getCollectUrl(): string {
77
89
  const PING_INTERVAL_MS = 24 * 60 * 60 * 1000;
78
90
 
79
91
  let isTelemetryEnabled = true;
92
+ let _hasSearchFilterBeenUsed = false;
80
93
 
81
94
  export function setTelemetryEnabled(enabled: boolean): void {
82
95
  isTelemetryEnabled = enabled;
83
96
  }
84
97
 
98
+ export function markSearchFilterUsed(): void {
99
+ _hasSearchFilterBeenUsed = true;
100
+ }
101
+
85
102
  async function getOrCreateClientId(): Promise<string> {
86
103
  try {
87
104
  const settings = await loadSettings();
88
- if (settings.telemetryClientId) {
89
- return settings.telemetryClientId;
105
+ if (settings.telemetryClientId || settings.telemetryDeviceId) {
106
+ const id = settings.telemetryClientId || settings.telemetryDeviceId || '';
107
+ if (!settings.telemetryDeviceId) {
108
+ await saveSettings({...settings, telemetryDeviceId: id});
109
+ }
110
+ return id;
90
111
  }
91
112
  const part1 = Math.floor(100000000 + Math.random() * 900000000);
92
113
  const part2 = Math.floor(1000000000 + Math.random() * 9000000000);
93
114
  const newId = `${part1}.${part2}`;
94
- saveSettings({...settings, telemetryClientId: newId});
115
+ const now = Date.now();
116
+ await saveSettings({
117
+ ...settings,
118
+ telemetryClientId: newId,
119
+ telemetryDeviceId: newId,
120
+ deviceFirstSeen: settings.deviceFirstSeen || now,
121
+ });
95
122
  return newId;
96
123
  } catch {
97
124
  return `${Math.floor(100000000 + Math.random() * 900000000)}.${Math.floor(1000000000 + Math.random() * 9000000000)}`;
@@ -130,7 +157,27 @@ function isDeviceEmulator(): boolean {
130
157
  return false;
131
158
  }
132
159
 
133
- function getSystemMetadata(environment?: string) {
160
+ function getDeviceLocaleInfo(): {countryCode: string; locale: string; timeZone: string} {
161
+ try {
162
+ const opts = typeof Intl !== 'undefined' && Intl.DateTimeFormat ? Intl.DateTimeFormat().resolvedOptions() : {} as any;
163
+ const timeZone = opts.timeZone || 'unknown';
164
+ const locale = opts.locale || 'unknown';
165
+ let countryCode = 'unknown';
166
+
167
+ if (locale && locale !== 'unknown') {
168
+ const parts = locale.split(/[-_]/);
169
+ if (parts.length > 1) {
170
+ countryCode = parts[parts.length - 1].toUpperCase();
171
+ }
172
+ }
173
+
174
+ return { countryCode, locale, timeZone };
175
+ } catch {
176
+ return { countryCode: 'unknown', locale: 'unknown', timeZone: 'unknown' };
177
+ }
178
+ }
179
+
180
+ function getSystemMetadata(environment?: string, currentSettings?: PersistedSettings) {
134
181
  const rnConstants = Platform.constants as any;
135
182
  const rnVersion = rnConstants?.reactNativeVersion
136
183
  ? `${rnConstants.reactNativeVersion.major}.${rnConstants.reactNativeVersion.minor}.${rnConstants.reactNativeVersion.patch}`
@@ -138,20 +185,172 @@ function getSystemMetadata(environment?: string) {
138
185
 
139
186
  const isHermes = Boolean(typeof (global as any).HermesInternal === 'object' && (global as any).HermesInternal !== null);
140
187
  const isFabric = Boolean((global as any)?.nativeFabricUIManager !== undefined);
188
+ const isBridgeless = Boolean((global as any)?._rn_bridgeless || (global as any)?.RN$Bridgeless);
141
189
  const isDev = Boolean(typeof __DEV__ !== 'undefined' && __DEV__);
142
190
  const isEmulator = isDeviceEmulator();
191
+ const { countryCode, locale, timeZone } = getDeviceLocaleInfo();
192
+
193
+ let screenRes = 'unknown';
194
+ let screenDensity = 1;
195
+ let fontScale = 1;
196
+ let deviceFormFactor = 'phone';
197
+ let screenOrientation = 'portrait';
198
+ try {
199
+ const dim = Dimensions.get('window');
200
+ screenRes = `${Math.round(dim.width)}x${Math.round(dim.height)}`;
201
+ screenDensity = PixelRatio.get();
202
+ fontScale = PixelRatio.getFontScale();
203
+ screenOrientation = dim.width > dim.height ? 'landscape' : 'portrait';
204
+ const isTablet = (Platform.OS === 'ios' && Boolean((Platform as any).isPad)) ||
205
+ (Math.min(dim.width, dim.height) >= 600);
206
+ deviceFormFactor = isTablet ? 'tablet' : 'phone';
207
+ } catch {}
208
+
209
+ let appearanceMode = 'unknown';
210
+ try {
211
+ appearanceMode = Appearance?.getColorScheme?.() || 'unknown';
212
+ } catch {}
213
+
214
+ const isExpo = Boolean(
215
+ (globalThis as any)?.expo ||
216
+ (globalThis as any)?.__expo ||
217
+ (globalThis as any)?.__turboModuleProxy?.name === 'Expo' ||
218
+ (Platform.constants as any)?.ExpoVersion
219
+ );
220
+
221
+ let hermesVersion = 'non_hermes';
222
+ let jsEngine = 'jsc';
223
+ if (isHermes) {
224
+ jsEngine = 'hermes';
225
+ try {
226
+ const props = (global as any).HermesInternal?.getRuntimeProperties?.() || {};
227
+ hermesVersion = props['OSS Release Version'] || (props['Bytecode Version'] ? `bc_v${props['Bytecode Version']}` : 'hermes');
228
+ } catch {
229
+ hermesVersion = 'hermes';
230
+ }
231
+ } else if ((globalThis as any)?._v8) {
232
+ jsEngine = 'v8';
233
+ }
234
+
235
+ let reduxAttached = false;
236
+ try {
237
+ reduxAttached = isReduxConnected();
238
+ } catch {}
239
+
240
+ let analyticsAttached = false;
241
+ try {
242
+ analyticsAttached = isFirebaseAnalyticsPatched();
243
+ } catch {}
244
+
245
+ const hasNativeModule = isNativeModuleAvailable();
246
+ const hasMmkv = Boolean(NativeModules?.RNMMKV || (globalThis as any)?.MMKV);
247
+
248
+ let capturedCrashesCount = 0;
249
+ try {
250
+ capturedCrashesCount = getCrashRecords().length;
251
+ } catch {}
252
+
253
+ let capturedApisBracket = '0';
254
+ try {
255
+ const apiCount = getNetworkLogs().length;
256
+ capturedApisBracket =
257
+ apiCount === 0
258
+ ? '0'
259
+ : apiCount <= 10
260
+ ? '1-10'
261
+ : apiCount <= 50
262
+ ? '11-50'
263
+ : apiCount <= 100
264
+ ? '51-100'
265
+ : '100+';
266
+ } catch {}
267
+
268
+ let isMetroConnected = false;
269
+ try {
270
+ isMetroConnected = isLocalDebugEnvironment();
271
+ } catch {}
272
+
273
+ const defaultLandingTab = currentSettings?.defaultTab || 'apis';
274
+ const fabMode =
275
+ hasNativeModule && currentSettings?.useNativeFab !== false
276
+ ? 'native_ui_thread'
277
+ : 'js_floating_button';
278
+
279
+ const deviceBrand = String(
280
+ rnConstants?.Brand ||
281
+ (Platform.OS === 'ios' ? 'Apple' : 'unknown'),
282
+ );
283
+
284
+ const deviceModel = String(
285
+ rnConstants?.Model ||
286
+ rnConstants?.systemName ||
287
+ (Platform.OS === 'ios' ? 'iPhone/iPad' : 'unknown'),
288
+ );
289
+
290
+ const deviceManufacturer = String(
291
+ rnConstants?.Manufacturer ||
292
+ (Platform.OS === 'ios' ? 'Apple' : 'unknown'),
293
+ );
294
+
295
+ let appName = 'App';
296
+ let bundleId = 'unknown';
297
+ let appVersion = 'unknown';
298
+ let appBuild = 'unknown';
299
+ try {
300
+ appName = getAppName() || 'App';
301
+ bundleId = getBundleIdentifier() || 'unknown';
302
+ const verInfo = getAppVersionAndBuild();
303
+ appVersion = verInfo.version || 'unknown';
304
+ appBuild = verInfo.build || 'unknown';
305
+ } catch {}
306
+
307
+ const reactVer = typeof React !== 'undefined' && React.version ? React.version : 'unknown';
143
308
 
144
309
  return {
310
+ pkg_name: PKG_NAME,
311
+ pkg_version: LIB_VERSION,
145
312
  lib_version: LIB_VERSION,
313
+ install_type: isDev ? 'local_dev' : 'bundled_production',
314
+ app_name: appName,
315
+ bundle_id: bundleId,
316
+ app_version: appVersion,
317
+ app_build: appBuild,
318
+ react_version: reactVer,
146
319
  platform: Platform.OS,
147
320
  os_version: String(Platform.Version || 'unknown'),
148
321
  rn_version: rnVersion,
322
+ country_code: countryCode,
323
+ device_locale: locale,
324
+ time_zone: timeZone,
325
+ device_brand: deviceBrand,
326
+ device_model: deviceModel,
327
+ device_manufacturer: deviceManufacturer,
328
+ device_form_factor: deviceFormFactor,
329
+ screen_resolution: screenRes,
330
+ screen_density: screenDensity,
331
+ screen_orientation: screenOrientation,
332
+ font_scale: fontScale,
333
+ appearance_mode: appearanceMode,
334
+ is_expo: isExpo,
335
+ js_engine: jsEngine,
336
+ hermes_version: hermesVersion,
149
337
  is_hermes: isHermes,
150
338
  is_fabric: isFabric,
339
+ is_bridgeless: isBridgeless,
340
+ has_native_module: hasNativeModule,
341
+ has_mmkv: hasMmkv,
342
+ fab_mode: fabMode,
343
+ default_landing_tab: defaultLandingTab,
344
+ captured_crashes_count: capturedCrashesCount,
345
+ captured_apis_bracket: capturedApisBracket,
346
+ search_filter_used: _hasSearchFilterBeenUsed,
347
+ is_metro_connected: isMetroConnected,
151
348
  is_dev: isDev,
152
349
  is_emulator: isEmulator,
153
350
  device_type: isEmulator ? 'emulator' : 'real_device',
154
351
  app_mode: isDev ? 'local_mode' : 'bundled_application',
352
+ redux_attached: reduxAttached,
353
+ analytics_attached: analyticsAttached,
155
354
  environment: environment || (isDev ? 'DEV' : 'PROD'),
156
355
  engagement_time_msec: 1000,
157
356
  };
@@ -174,24 +373,196 @@ export async function trackActiveTelemetryHeartbeat(
174
373
  const settings = await loadSettings();
175
374
  const lastPing = settings.telemetryLastPing || 0;
176
375
  const now = Date.now();
376
+ const isNewDevice = !settings.deviceRegistered;
177
377
 
178
- if (now - lastPing < PING_INTERVAL_MS) return;
378
+ // Send on first launch or if 24h passed since last ping
379
+ if (!isNewDevice && now - lastPing < PING_INTERVAL_MS) return;
179
380
 
180
381
  const clientId = await getOrCreateClientId();
181
- const params = getSystemMetadata(environment);
382
+ const params = getSystemMetadata(environment, settings);
383
+
384
+ const deviceParams = {
385
+ ...params,
386
+ device_id: clientId,
387
+ is_new_device: isNewDevice,
388
+ device_first_seen: settings.deviceFirstSeen || now,
389
+ };
390
+
391
+ const eventsToDispatch = isNewDevice
392
+ ? [
393
+ {name: 'active_device', params: deviceParams},
394
+ {name: 'first_device_install', params: deviceParams},
395
+ {name: 'inspector_active_heartbeat', params: deviceParams},
396
+ ]
397
+ : [
398
+ {name: 'active_device', params: deviceParams},
399
+ {name: 'inspector_active_heartbeat', params: deviceParams},
400
+ ];
182
401
 
183
402
  await fetch(url, {
184
403
  method: 'POST',
185
404
  headers: {'Content-Type': 'application/json'},
186
405
  body: JSON.stringify({
187
406
  client_id: clientId,
407
+ user_id: clientId,
188
408
  non_personalized_ads: true,
189
- events: [{name: 'inspector_active_heartbeat', params}],
409
+ user_properties: {
410
+ device_id: { value: clientId },
411
+ pkg_name: { value: params.pkg_name },
412
+ pkg_version: { value: params.pkg_version },
413
+ install_type: { value: params.install_type },
414
+ app_name: { value: params.app_name },
415
+ bundle_id: { value: params.bundle_id },
416
+ app_version: { value: params.app_version },
417
+ react_version: { value: params.react_version },
418
+ country: { value: params.country_code },
419
+ country_code: { value: params.country_code },
420
+ locale: { value: params.device_locale },
421
+ time_zone: { value: params.time_zone },
422
+ device_brand: { value: params.device_brand },
423
+ device_model: { value: params.device_model },
424
+ device_form_factor: { value: params.device_form_factor },
425
+ screen_resolution: { value: params.screen_resolution },
426
+ appearance_mode: { value: params.appearance_mode },
427
+ is_expo: { value: String(params.is_expo) },
428
+ js_engine: { value: params.js_engine },
429
+ is_fabric: { value: String(params.is_fabric) },
430
+ is_bridgeless: { value: String(params.is_bridgeless) },
431
+ has_native_module: { value: String(params.has_native_module) },
432
+ has_mmkv: { value: String(params.has_mmkv) },
433
+ fab_mode: { value: params.fab_mode },
434
+ default_landing_tab: { value: params.default_landing_tab },
435
+ captured_crashes_count: { value: String(params.captured_crashes_count) },
436
+ captured_apis_bracket: { value: params.captured_apis_bracket },
437
+ search_filter_used: { value: String(params.search_filter_used) },
438
+ is_metro_connected: { value: String(params.is_metro_connected) },
439
+ redux_attached: { value: String(params.redux_attached) },
440
+ analytics_attached: { value: String(params.analytics_attached) },
441
+ platform: { value: params.platform },
442
+ rn_version: { value: params.rn_version },
443
+ lib_version: { value: params.lib_version },
444
+ },
445
+ events: eventsToDispatch,
190
446
  }),
191
447
  });
192
448
 
193
- saveSettings({...settings, telemetryLastPing: now});
449
+ await saveSettings({
450
+ ...settings,
451
+ telemetryClientId: clientId,
452
+ telemetryDeviceId: clientId,
453
+ deviceRegistered: true,
454
+ deviceFirstSeen: settings.deviceFirstSeen || now,
455
+ telemetryLastPing: now,
456
+ telemetryLastActive: now,
457
+ });
194
458
  } catch {
195
459
  // silent
196
460
  }
197
461
  }
462
+
463
+ export async function trackTelemetryEvent(
464
+ eventName: string,
465
+ eventParams?: Record<string, any>,
466
+ ): Promise<void> {
467
+ if (!isTelemetryEnabled) return;
468
+ const url = getCollectUrl();
469
+ if (!url) return;
470
+
471
+ try {
472
+ const clientId = await getOrCreateClientId();
473
+ const settings = await loadSettings().catch(() => ({} as PersistedSettings));
474
+ const metadata = getSystemMetadata(undefined, settings);
475
+
476
+ const params = {
477
+ ...metadata,
478
+ device_id: clientId,
479
+ ...eventParams,
480
+ engagement_time_msec: 100,
481
+ };
482
+
483
+ await fetch(url, {
484
+ method: 'POST',
485
+ headers: {'Content-Type': 'application/json'},
486
+ body: JSON.stringify({
487
+ client_id: clientId,
488
+ user_id: clientId,
489
+ non_personalized_ads: true,
490
+ user_properties: {
491
+ device_id: { value: clientId },
492
+ pkg_name: { value: metadata.pkg_name },
493
+ pkg_version: { value: metadata.pkg_version },
494
+ install_type: { value: metadata.install_type },
495
+ app_name: { value: metadata.app_name },
496
+ bundle_id: { value: metadata.bundle_id },
497
+ app_version: { value: metadata.app_version },
498
+ react_version: { value: metadata.react_version },
499
+ country: { value: metadata.country_code },
500
+ country_code: { value: metadata.country_code },
501
+ locale: { value: metadata.device_locale },
502
+ time_zone: { value: metadata.time_zone },
503
+ device_brand: { value: metadata.device_brand },
504
+ device_model: { value: metadata.device_model },
505
+ device_form_factor: { value: metadata.device_form_factor },
506
+ screen_resolution: { value: metadata.screen_resolution },
507
+ appearance_mode: { value: metadata.appearance_mode },
508
+ is_expo: { value: String(metadata.is_expo) },
509
+ js_engine: { value: metadata.js_engine },
510
+ is_fabric: { value: String(metadata.is_fabric) },
511
+ is_bridgeless: { value: String(metadata.is_bridgeless) },
512
+ has_native_module: { value: String(metadata.has_native_module) },
513
+ has_mmkv: { value: String(metadata.has_mmkv) },
514
+ fab_mode: { value: metadata.fab_mode },
515
+ default_landing_tab: { value: metadata.default_landing_tab },
516
+ captured_crashes_count: { value: String(metadata.captured_crashes_count) },
517
+ captured_apis_bracket: { value: metadata.captured_apis_bracket },
518
+ search_filter_used: { value: String(metadata.search_filter_used) },
519
+ is_metro_connected: { value: String(metadata.is_metro_connected) },
520
+ redux_attached: { value: String(metadata.redux_attached) },
521
+ analytics_attached: { value: String(metadata.analytics_attached) },
522
+ platform: { value: metadata.platform },
523
+ rn_version: { value: metadata.rn_version },
524
+ lib_version: { value: metadata.lib_version },
525
+ },
526
+ events: [{ name: eventName, params }],
527
+ }),
528
+ });
529
+ } catch {
530
+ // silent
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Tracks when a developer opens/switches tabs in the In-App Inspector (e.g. APIs, Logs, Redux, Performance, Storage, etc.)
536
+ */
537
+ export function trackInspectorTabView(tabName: string): void {
538
+ trackTelemetryEvent('inspector_tab_view', {
539
+ tab_name: tabName,
540
+ screen_name: `tab_${tabName}`,
541
+ screen_class: 'NetworkInspector',
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Tracks screen views within the In-App Inspector
547
+ */
548
+ export function trackInspectorScreenView(screenName: string): void {
549
+ trackTelemetryEvent('screen_view', {
550
+ screen_name: screenName,
551
+ screen_class: 'NetworkInspector',
552
+ });
553
+ }
554
+
555
+ /**
556
+ * Tracks screen / performance recording events
557
+ */
558
+ export function trackInspectorScreenRecord(
559
+ action: 'start' | 'stop' | 'toggle',
560
+ recordingType = 'screen',
561
+ ): void {
562
+ trackTelemetryEvent('inspector_screen_record', {
563
+ action,
564
+ recording_type: recordingType,
565
+ });
566
+ }
567
+
568
+
package/src/index.tsx CHANGED
@@ -87,6 +87,8 @@ import {
87
87
  import {
88
88
  trackActiveTelemetryHeartbeat,
89
89
  setTelemetryEnabled,
90
+ trackInspectorTabView,
91
+ trackInspectorScreenView,
90
92
  } from './helpers/telemetry';
91
93
 
92
94
  import {
@@ -124,6 +126,11 @@ import {
124
126
  isNativeModuleAvailable,
125
127
  } from './native/NativeInspector';
126
128
 
129
+ import {
130
+ fetchRemoteConfigModuleStatus,
131
+ isFirebaseRemoteConfigAvailable,
132
+ } from './helpers/remoteConfig';
133
+
127
134
  // Constants
128
135
  import {
129
136
  StatusFilter,
@@ -157,6 +164,7 @@ const NetworkInspector = ({
157
164
  environment,
158
165
  initialVisible = false,
159
166
  visible: controlledVisible,
167
+ remoteConfig,
160
168
  }: NetworkInspectorProps): React.JSX.Element | null => {
161
169
  // Set custom storage synchronously during render phase
162
170
  setCustomStorage(storage || null);
@@ -352,6 +360,7 @@ const NetworkInspector = ({
352
360
  | 'redux'
353
361
  | null
354
362
  >(null);
363
+ const [isFeedbackOpen, setIsFeedbackOpen] = useState<boolean>(false);
355
364
  const [settingsActiveSubTab, setSettingsActiveSubTab] = useState<SettingsSubTab>('module');
356
365
  const [tabVisibility, setTabVisibility] = useState<
357
366
  Record<ActiveTab, boolean>
@@ -513,6 +522,28 @@ const NetworkInspector = ({
513
522
  };
514
523
  }, []);
515
524
 
525
+ // Fetch and apply module enable/disable statuses from Firebase Remote Config if configured or available
526
+ useEffect(() => {
527
+ let isMounted = true;
528
+ if (remoteConfig === false) return;
529
+
530
+ fetchRemoteConfigModuleStatus(typeof remoteConfig === 'object' ? remoteConfig : undefined)
531
+ .then(remoteModules => {
532
+ if (isMounted && remoteModules) {
533
+ setTabVisibility(prev => ({
534
+ ...prev,
535
+ ...remoteModules,
536
+ apis: remoteModules.apis ?? true,
537
+ }));
538
+ }
539
+ })
540
+ .catch(() => {});
541
+
542
+ return () => {
543
+ isMounted = false;
544
+ };
545
+ }, [remoteConfig]);
546
+
516
547
  useEffect(() => {
517
548
  if (!settingsHydratedRef.current) return;
518
549
  saveSettings({
@@ -655,6 +686,7 @@ const NetworkInspector = ({
655
686
  setActiveTab(key);
656
687
  loadNativePage(key);
657
688
  }
689
+ trackInspectorTabView(key);
658
690
  }, [loadNativePage]);
659
691
 
660
692
  const [selectedEvent, setSelectedEvent] = useState<AnalyticsEvent | null>(
@@ -1013,11 +1045,13 @@ const NetworkInspector = ({
1013
1045
  useEffect(() => {
1014
1046
  isVisibleRefObj.current = visible;
1015
1047
  if (visible) {
1048
+ trackInspectorScreenView('InspectorModal');
1016
1049
  const target =
1017
1050
  defaultTab === 'apis' || tabVisibility?.[defaultTab]
1018
1051
  ? defaultTab
1019
1052
  : 'apis';
1020
1053
  setActiveTab(target);
1054
+ trackInspectorTabView(target);
1021
1055
 
1022
1056
 
1023
1057
 
@@ -2032,6 +2066,8 @@ const NetworkInspector = ({
2032
2066
  setShowHeaderInfo,
2033
2067
  settingsPage,
2034
2068
  setSettingsPage,
2069
+ isFeedbackOpen,
2070
+ setIsFeedbackOpen,
2035
2071
  updateAvailable,
2036
2072
  latestNpmVersion,
2037
2073
  clearAnim,
@@ -2207,6 +2243,7 @@ const NetworkInspector = ({
2207
2243
  selectedLog,
2208
2244
  showHeaderInfo,
2209
2245
  settingsPage,
2246
+ isFeedbackOpen,
2210
2247
  updateAvailable,
2211
2248
  latestNpmVersion,
2212
2249
  clearAnim,
@@ -2562,4 +2599,9 @@ export {
2562
2599
  I18nextProvider,
2563
2600
  } from './i18n';
2564
2601
 
2602
+ export {
2603
+ fetchRemoteConfigModuleStatus,
2604
+ isFirebaseRemoteConfigAvailable,
2605
+ } from './helpers/remoteConfig';
2606
+
2565
2607
  export { LIB_VERSION } from './constants/version';
@@ -1,6 +1,7 @@
1
1
  export interface AppFontConfig {
2
2
  interRegular?: string;
3
3
  interMedium?: string;
4
+ interSemiBold?: string;
4
5
  interBold?: string;
5
6
  Sfprotext?: string;
6
7
  }
@@ -8,6 +9,7 @@ export interface AppFontConfig {
8
9
  export const AppFonts = {
9
10
  interRegular: 'Inter-Regular',
10
11
  interMedium: 'Inter-Medium',
12
+ interSemiBold: 'Inter-SemiBold',
11
13
  interBold: 'Inter-Bold',
12
14
  Sfprotext: 'Inter-Regular',
13
15
  };
@@ -140,7 +140,26 @@ export const getRawStyles = (colors: typeof AppColors) => ({
140
140
  },
141
141
 
142
142
  listContent: {paddingBottom: Platform.OS === 'ios' ? 44 : 32},
143
- // #2 scroll-to-top button, always shown at the bottom right.
143
+ // Floating Support & Feedback FAB, in place of scroll-to-top button
144
+ supportFab: {
145
+ position: 'absolute',
146
+ bottom: 22,
147
+ right: 16,
148
+ width: 42,
149
+ height: 42,
150
+ borderRadius: 21,
151
+ backgroundColor: colors.purple,
152
+ alignItems: 'center',
153
+ justifyContent: 'center',
154
+ shadowColor: colors.purple,
155
+ shadowOffset: {width: 0, height: 4},
156
+ shadowOpacity: 0.45,
157
+ shadowRadius: 8,
158
+ elevation: 14,
159
+ zIndex: 999,
160
+ borderWidth: 1.5,
161
+ borderColor: 'rgba(255, 255, 255, 0.25)',
162
+ },
144
163
  scrollTopBtn: {
145
164
  position: 'absolute',
146
165
  bottom: 22,
@@ -81,6 +81,7 @@ export const SettingsSubTab = {
81
81
  Module: 'module',
82
82
  Ui: 'ui',
83
83
  Limits: 'limits',
84
+ About: 'about',
84
85
  } as const;
85
86
  export type SettingsSubTab =
86
87
  (typeof SettingsSubTab)[keyof typeof SettingsSubTab];
@@ -180,8 +180,17 @@ export interface PersistedSettings {
180
180
  reduxExpandDepth?: number;
181
181
  showDuplicateLogs?: boolean;
182
182
  showUpdateToast?: boolean;
183
+ useNativeFab?: boolean;
184
+ shakeToOpen?: boolean;
183
185
  telemetryClientId?: string;
186
+ telemetryDeviceId?: string;
187
+ deviceRegistered?: boolean;
188
+ deviceFirstSeen?: number;
184
189
  telemetryLastPing?: number;
190
+ telemetryLastActive?: number;
191
+ starPromptActioned?: boolean;
192
+ starPromptLastShown?: number;
193
+ starPromptFirstSeen?: number;
185
194
  }
186
195
 
187
196
  // ─── Inspector component props / context ──────────────────────────────────────
@@ -195,6 +204,7 @@ export interface NetworkInspectorProps {
195
204
  environment?: 'DEV' | 'UAT' | 'PrePROD' | 'PROD' | 'QA' | 'Staging' | string;
196
205
  initialVisible?: boolean;
197
206
  visible?: boolean;
207
+ remoteConfig?: boolean | any;
198
208
  }
199
209
 
200
210
  export interface NavigationTrackerProps {
@@ -239,6 +249,8 @@ export interface InspectorContextValue {
239
249
  setShowHeaderInfo: React.Dispatch<React.SetStateAction<boolean>>;
240
250
  settingsPage: SettingsPage;
241
251
  setSettingsPage: React.Dispatch<React.SetStateAction<SettingsPage>>;
252
+ isFeedbackOpen: boolean;
253
+ setIsFeedbackOpen: React.Dispatch<React.SetStateAction<boolean>>;
242
254
  updateAvailable: boolean;
243
255
  latestNpmVersion: string | null;
244
256
  clearAnim: Animated.Value;