react-native-inapp-inspector 2.3.0 → 2.3.2

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.
@@ -0,0 +1,206 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {Animated, StyleSheet, View} from 'react-native';
3
+ import {AppColors} from '../styles/AppColors';
4
+
5
+ interface SkeletonPlaceholderProps {
6
+ cardCount?: number;
7
+ }
8
+
9
+ const SkeletonPlaceholder = React.memo(function SkeletonPlaceholder({
10
+ cardCount = 4,
11
+ }: SkeletonPlaceholderProps) {
12
+ const shimmerAnim = useRef(new Animated.Value(0.35)).current;
13
+
14
+ useEffect(() => {
15
+ const animation = Animated.loop(
16
+ Animated.sequence([
17
+ Animated.timing(shimmerAnim, {
18
+ toValue: 0.85,
19
+ duration: 750,
20
+ useNativeDriver: true,
21
+ }),
22
+ Animated.timing(shimmerAnim, {
23
+ toValue: 0.35,
24
+ duration: 750,
25
+ useNativeDriver: true,
26
+ }),
27
+ ]),
28
+ );
29
+ animation.start();
30
+ return () => animation.stop();
31
+ }, [shimmerAnim]);
32
+
33
+ return (
34
+ <View style={skeletonStyles.container}>
35
+ {/* ─── Search & Scope Toolbar Skeleton ─── */}
36
+ <View style={skeletonStyles.toolbarSkeleton}>
37
+ <Animated.View
38
+ style={[
39
+ skeletonStyles.searchBarSkeleton,
40
+ {opacity: shimmerAnim},
41
+ ]}
42
+ />
43
+ <View style={skeletonStyles.actionButtonsRow}>
44
+ <Animated.View
45
+ style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
46
+ />
47
+ <Animated.View
48
+ style={[skeletonStyles.iconButtonSkeleton, {opacity: shimmerAnim}]}
49
+ />
50
+ </View>
51
+ </View>
52
+
53
+ {/* ─── Quick Filter Chips Skeleton Strip ─── */}
54
+ <View style={skeletonStyles.chipStripSkeleton}>
55
+ <Animated.View
56
+ style={[skeletonStyles.chipSkeleton, {width: 48, opacity: shimmerAnim}]}
57
+ />
58
+ <Animated.View
59
+ style={[skeletonStyles.chipSkeleton, {width: 68, opacity: shimmerAnim}]}
60
+ />
61
+ <Animated.View
62
+ style={[skeletonStyles.chipSkeleton, {width: 76, opacity: shimmerAnim}]}
63
+ />
64
+ <Animated.View
65
+ style={[skeletonStyles.chipSkeleton, {width: 58, opacity: shimmerAnim}]}
66
+ />
67
+ </View>
68
+
69
+ {/* ─── List Cards Skeleton ─── */}
70
+ {Array.from({length: cardCount}).map((_, i) => (
71
+ <Animated.View
72
+ key={`skeleton_card_${i}`}
73
+ style={[skeletonStyles.cardSkeleton, {opacity: shimmerAnim}]}>
74
+ {/* Top row: Status pill + Method + Time */}
75
+ <View style={skeletonStyles.cardTopRow}>
76
+ <View style={skeletonStyles.badgeGroup}>
77
+ <View style={skeletonStyles.statusBadgeSkeleton} />
78
+ <View style={skeletonStyles.methodBadgeSkeleton} />
79
+ </View>
80
+ <View style={skeletonStyles.timeSkeleton} />
81
+ </View>
82
+
83
+ {/* Middle row: URL lines */}
84
+ <View style={skeletonStyles.urlLineLong} />
85
+ <View style={skeletonStyles.urlLineShort} />
86
+
87
+ {/* Bottom row: Latency & Size */}
88
+ <View style={skeletonStyles.cardBottomRow}>
89
+ <View style={skeletonStyles.metaPillSkeleton} />
90
+ <View style={skeletonStyles.metaPillSkeleton} />
91
+ </View>
92
+ </Animated.View>
93
+ ))}
94
+ </View>
95
+ );
96
+ });
97
+
98
+ const skeletonStyles = StyleSheet.create({
99
+ container: {
100
+ flex: 1,
101
+ paddingHorizontal: 12,
102
+ paddingTop: 8,
103
+ },
104
+ toolbarSkeleton: {
105
+ flexDirection: 'row',
106
+ alignItems: 'center',
107
+ gap: 8,
108
+ marginBottom: 8,
109
+ },
110
+ searchBarSkeleton: {
111
+ flex: 1,
112
+ height: 36,
113
+ borderRadius: 8,
114
+ backgroundColor: AppColors.graySurface,
115
+ borderWidth: 1,
116
+ borderColor: AppColors.dividerColor,
117
+ },
118
+ actionButtonsRow: {
119
+ flexDirection: 'row',
120
+ gap: 6,
121
+ },
122
+ iconButtonSkeleton: {
123
+ width: 36,
124
+ height: 36,
125
+ borderRadius: 8,
126
+ backgroundColor: AppColors.graySurface,
127
+ borderWidth: 1,
128
+ borderColor: AppColors.dividerColor,
129
+ },
130
+ chipStripSkeleton: {
131
+ flexDirection: 'row',
132
+ gap: 6,
133
+ marginBottom: 10,
134
+ },
135
+ chipSkeleton: {
136
+ height: 24,
137
+ borderRadius: 6,
138
+ backgroundColor: AppColors.graySurface,
139
+ borderWidth: 1,
140
+ borderColor: AppColors.dividerColor,
141
+ },
142
+ cardSkeleton: {
143
+ backgroundColor: AppColors.primaryLight,
144
+ borderRadius: 10,
145
+ padding: 12,
146
+ marginBottom: 8,
147
+ borderWidth: 1,
148
+ borderColor: AppColors.dividerColor,
149
+ },
150
+ cardTopRow: {
151
+ flexDirection: 'row',
152
+ justifyContent: 'space-between',
153
+ alignItems: 'center',
154
+ marginBottom: 8,
155
+ },
156
+ badgeGroup: {
157
+ flexDirection: 'row',
158
+ alignItems: 'center',
159
+ gap: 6,
160
+ },
161
+ statusBadgeSkeleton: {
162
+ width: 38,
163
+ height: 18,
164
+ borderRadius: 4,
165
+ backgroundColor: AppColors.graySurface,
166
+ },
167
+ methodBadgeSkeleton: {
168
+ width: 44,
169
+ height: 18,
170
+ borderRadius: 4,
171
+ backgroundColor: AppColors.graySurface,
172
+ },
173
+ timeSkeleton: {
174
+ width: 48,
175
+ height: 12,
176
+ borderRadius: 4,
177
+ backgroundColor: AppColors.graySurface,
178
+ },
179
+ urlLineLong: {
180
+ height: 13,
181
+ borderRadius: 4,
182
+ backgroundColor: AppColors.graySurface,
183
+ marginBottom: 5,
184
+ width: '90%',
185
+ },
186
+ urlLineShort: {
187
+ height: 11,
188
+ borderRadius: 4,
189
+ backgroundColor: AppColors.graySurface,
190
+ marginBottom: 8,
191
+ width: '55%',
192
+ },
193
+ cardBottomRow: {
194
+ flexDirection: 'row',
195
+ gap: 8,
196
+ marginTop: 2,
197
+ },
198
+ metaPillSkeleton: {
199
+ width: 52,
200
+ height: 14,
201
+ borderRadius: 4,
202
+ backgroundColor: AppColors.graySurface,
203
+ },
204
+ });
205
+
206
+ export default SkeletonPlaceholder;
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED FILE — do not edit by hand.
2
2
  // Regenerated from package.json on every build by scripts/gen-version.js.
3
- export const LIB_VERSION = '2.3.0';
3
+ export const LIB_VERSION = '2.3.2';
@@ -273,13 +273,13 @@ export const calculateByteSize = (str: string): number => {
273
273
  };
274
274
 
275
275
  /**
276
- * Determine type and format value safely
276
+ * Determine type and format value safely without blocking JS thread
277
277
  */
278
278
  export const analyzeStorageValue = (
279
279
  rawVal: any,
280
280
  ): {
281
281
  strVal: string;
282
- parsedVal?: any;
282
+ parsedValue?: any;
283
283
  type: 'json' | 'string' | 'number' | 'boolean' | 'null';
284
284
  byteSize: number;
285
285
  } => {
@@ -288,14 +288,14 @@ export const analyzeStorageValue = (
288
288
  }
289
289
 
290
290
  const strVal = typeof rawVal === 'string' ? rawVal : String(rawVal);
291
- const byteSize = calculateByteSize(strVal);
291
+ const byteSize = strVal.length * 2;
292
292
 
293
293
  if (typeof rawVal === 'number') {
294
- return {strVal, parsedVal: rawVal, type: 'number', byteSize};
294
+ return {strVal, parsedValue: rawVal, type: 'number', byteSize};
295
295
  }
296
296
 
297
297
  if (typeof rawVal === 'boolean') {
298
- return {strVal, parsedVal: rawVal, type: 'boolean', byteSize};
298
+ return {strVal, parsedValue: rawVal, type: 'boolean', byteSize};
299
299
  }
300
300
 
301
301
  const trimmed = strVal.trim();
@@ -303,18 +303,22 @@ export const analyzeStorageValue = (
303
303
  (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
304
304
  (trimmed.startsWith('[') && trimmed.endsWith(']'))
305
305
  ) {
306
- try {
307
- const parsed = JSON.parse(trimmed);
308
- return {strVal, parsedVal: parsed, type: 'json', byteSize};
309
- } catch {}
306
+ // For small/medium JSON, parse eagerly; for huge payloads (>10KB), parse lazily on expand
307
+ if (trimmed.length < 10000) {
308
+ try {
309
+ const parsed = JSON.parse(trimmed);
310
+ return {strVal, parsedValue: parsed, type: 'json', byteSize};
311
+ } catch {}
312
+ }
313
+ return {strVal, type: 'json', byteSize};
310
314
  }
311
315
 
312
316
  if (trimmed === 'true' || trimmed === 'false') {
313
- return {strVal, parsedVal: trimmed === 'true', type: 'boolean', byteSize};
317
+ return {strVal, parsedValue: trimmed === 'true', type: 'boolean', byteSize};
314
318
  }
315
319
 
316
320
  if (!isNaN(Number(trimmed)) && trimmed !== '') {
317
- return {strVal, parsedVal: Number(trimmed), type: 'number', byteSize};
321
+ return {strVal, parsedValue: Number(trimmed), type: 'number', byteSize};
318
322
  }
319
323
 
320
324
  return {strVal, type: 'string', byteSize};
@@ -346,7 +350,7 @@ export const fetchStorageEntries = async (
346
350
  entries.push({
347
351
  key: k,
348
352
  value: analysis.strVal,
349
- parsedValue: analysis.parsedVal,
353
+ parsedValue: analysis.parsedValue,
350
354
  type: analysis.type,
351
355
  byteSize: analysis.byteSize,
352
356
  });
@@ -358,7 +362,7 @@ export const fetchStorageEntries = async (
358
362
  entries.push({
359
363
  key: k,
360
364
  value: analysis.strVal,
361
- parsedValue: analysis.parsedVal,
365
+ parsedValue: analysis.parsedValue,
362
366
  type: analysis.type,
363
367
  byteSize: analysis.byteSize,
364
368
  });
@@ -397,7 +401,7 @@ export const fetchStorageEntries = async (
397
401
  entries.push({
398
402
  key: k,
399
403
  value: analysis.strVal,
400
- parsedValue: analysis.parsedVal,
404
+ parsedValue: analysis.parsedValue,
401
405
  type: analysis.type,
402
406
  byteSize: analysis.byteSize,
403
407
  });
package/src/index.tsx CHANGED
@@ -146,14 +146,13 @@ import {toggleGlobalTheme} from './styles';
146
146
 
147
147
  const NetworkInspector = ({
148
148
  enabled = true,
149
- isEnabled = true,
150
149
  storage,
151
150
  navigationRef,
152
151
  appIcon,
153
152
  environment,
154
- initialVisible,
153
+ initialVisible = false,
155
154
  visible: controlledVisible,
156
- }: NetworkInspectorProps): React.JSX.Element => {
155
+ }: NetworkInspectorProps): React.JSX.Element | null => {
157
156
  // Set custom storage synchronously during render phase
158
157
  setCustomStorage(storage || null);
159
158
 
@@ -170,7 +169,7 @@ const NetworkInspector = ({
170
169
 
171
170
  const [logs, setLogs] = useState<NetworkLog[]>([]);
172
171
  const [visible, setVisible] = useState<boolean>(
173
- initialVisible ?? controlledVisible ?? false,
172
+ controlledVisible ?? initialVisible ?? false,
174
173
  );
175
174
 
176
175
  useEffect(() => {
@@ -945,24 +944,29 @@ const NetworkInspector = ({
945
944
 
946
945
  // 100% Native Main-Thread Floating Button Lifecycle
947
946
  useEffect(() => {
948
- if (!useNativeFab || !isEnabled || !enabled) {
947
+ if (!useNativeFab || !enabled) {
949
948
  if (useNativeFab) {
950
- hideNativeFloatingButton();
949
+ hideNativeFloatingButton().catch(() => {});
951
950
  }
952
951
  return;
953
952
  }
954
953
 
955
954
  if (visible) {
956
- hideNativeFloatingButton();
955
+ hideNativeFloatingButton().catch(() => {});
957
956
  } else {
958
- showNativeFloatingButton();
957
+ showNativeFloatingButton().catch(() => {});
959
958
  setNativeFloatingButtonBadge(
960
959
  logs.length > 0 || analyticsEvents.length > 0,
961
- );
960
+ ).catch(() => {});
962
961
  }
962
+
963
+ return () => {
964
+ if (useNativeFab) {
965
+ hideNativeFloatingButton().catch(() => {});
966
+ }
967
+ };
963
968
  }, [
964
969
  useNativeFab,
965
- isEnabled,
966
970
  enabled,
967
971
  visible,
968
972
  logs.length,
@@ -1027,21 +1031,12 @@ const NetworkInspector = ({
1027
1031
  if (freshCrashes.length > 0) {
1028
1032
  setCrashRecords(freshCrashes);
1029
1033
  }
1030
- }
1031
- // eslint-disable-next-line react-hooks/exhaustive-deps
1032
- }, [visible]);
1033
1034
 
1034
- useEffect(() => {
1035
- if (visible) {
1036
- const task = InteractionManager.runAfterInteractions(() => {
1035
+ const frame = requestAnimationFrame(() => {
1037
1036
  setIsReady(true);
1038
1037
  });
1039
- const fallbackTimer = setTimeout(() => {
1040
- setIsReady(true);
1041
- }, 350);
1042
1038
  return () => {
1043
- task.cancel();
1044
- clearTimeout(fallbackTimer);
1039
+ cancelAnimationFrame(frame);
1045
1040
  };
1046
1041
  } else {
1047
1042
  setIsReady(false);
@@ -1982,7 +1977,8 @@ const NetworkInspector = ({
1982
1977
  setVisible,
1983
1978
  closeModal,
1984
1979
  isReady,
1985
- isEnabled,
1980
+ enabled,
1981
+ isEnabled: enabled,
1986
1982
  appIcon,
1987
1983
  environment,
1988
1984
  modalHeightPercent,
@@ -2173,16 +2169,29 @@ const NetworkInspector = ({
2173
2169
  };
2174
2170
 
2175
2171
  const NetworkInspectorWrapper = (props: NetworkInspectorProps) => {
2176
- // If running in production release build and not explicitly force-enabled (e.g. for QA builds),
2177
- // return null immediately for 0-overhead production stubbing.
2178
- if (typeof __DEV__ !== 'undefined' && !__DEV__ && !props?.forceEnable) {
2172
+ const enabled = props?.enabled ?? true;
2173
+
2174
+ useEffect(() => {
2175
+ if (!enabled) {
2176
+ hideNativeFloatingButton().catch(() => {});
2177
+ }
2178
+ return () => {
2179
+ hideNativeFloatingButton().catch(() => {});
2180
+ };
2181
+ }, [enabled]);
2182
+
2183
+ // If enabled is false, return null immediately with 0 overhead
2184
+ if (!enabled) {
2179
2185
  return null;
2180
2186
  }
2181
2187
 
2182
2188
  return (
2183
2189
  <I18nextProvider i18n={i18n}>
2184
2190
  <ErrorBoundary fallbackType="inline">
2185
- <NetworkInspector {...props} />
2191
+ <NetworkInspector
2192
+ {...props}
2193
+ enabled={enabled}
2194
+ />
2186
2195
  </ErrorBoundary>
2187
2196
  </I18nextProvider>
2188
2197
  );
@@ -186,8 +186,6 @@ export interface PersistedSettings {
186
186
 
187
187
  export interface NetworkInspectorProps {
188
188
  enabled?: boolean;
189
- isEnabled?: boolean;
190
- forceEnable?: boolean;
191
189
  storage?: InspectorStorage;
192
190
  navigationRef?: any;
193
191
  appIcon?: any;
@@ -206,6 +204,7 @@ export interface InspectorContextValue {
206
204
  setVisible: React.Dispatch<React.SetStateAction<boolean>>;
207
205
  closeModal: () => void;
208
206
  isReady: boolean;
207
+ enabled: boolean;
209
208
  isEnabled: boolean;
210
209
  appIcon?: any;
211
210
  environment?: string;