react-native-inapp-inspector 2.0.1 → 2.0.3

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 (42) hide show
  1. package/README.md +10 -0
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +23 -0
  3. package/dist/commonjs/components/AppHeaderLogo.js +21 -2
  4. package/dist/commonjs/components/Inspector/InspectorHeader.js +84 -11
  5. package/dist/commonjs/components/Inspector/MainScreen.js +8 -0
  6. package/dist/commonjs/components/Inspector/NpmUpdateToast.d.ts +3 -0
  7. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +288 -0
  8. package/dist/commonjs/components/Inspector/SettingsPanel.js +192 -1
  9. package/dist/commonjs/components/Inspector/TelemetryConsentModal.d.ts +3 -0
  10. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +359 -0
  11. package/dist/commonjs/constants/version.d.ts +1 -1
  12. package/dist/commonjs/constants/version.js +1 -1
  13. package/dist/commonjs/helpers/index.d.ts +1 -0
  14. package/dist/commonjs/helpers/index.js +1 -0
  15. package/dist/commonjs/helpers/telemetry.d.ts +99 -0
  16. package/dist/commonjs/helpers/telemetry.js +398 -0
  17. package/dist/commonjs/index.d.ts +2 -0
  18. package/dist/commonjs/index.js +37 -3
  19. package/dist/commonjs/native/NativeInspector.d.ts +5 -0
  20. package/dist/commonjs/native/NativeInspector.js +3 -2
  21. package/dist/commonjs/types/interfaces.d.ts +5 -0
  22. package/dist/esm/components/AppHeaderLogo.js +21 -2
  23. package/dist/esm/components/Inspector/InspectorHeader.js +85 -12
  24. package/dist/esm/components/Inspector/MainScreen.js +8 -0
  25. package/dist/esm/components/Inspector/NpmUpdateToast.d.ts +3 -0
  26. package/dist/esm/components/Inspector/NpmUpdateToast.js +251 -0
  27. package/dist/esm/components/Inspector/SettingsPanel.js +192 -1
  28. package/dist/esm/components/Inspector/TelemetryConsentModal.d.ts +3 -0
  29. package/dist/esm/components/Inspector/TelemetryConsentModal.js +322 -0
  30. package/dist/esm/constants/version.d.ts +1 -1
  31. package/dist/esm/constants/version.js +1 -1
  32. package/dist/esm/helpers/index.d.ts +1 -0
  33. package/dist/esm/helpers/index.js +1 -0
  34. package/dist/esm/helpers/telemetry.d.ts +99 -0
  35. package/dist/esm/helpers/telemetry.js +380 -0
  36. package/dist/esm/index.d.ts +2 -0
  37. package/dist/esm/index.js +27 -3
  38. package/dist/esm/native/NativeInspector.d.ts +5 -0
  39. package/dist/esm/native/NativeInspector.js +3 -2
  40. package/dist/esm/types/interfaces.d.ts +5 -0
  41. package/ios/{NetworkInspectorModule.m → NetworkInspectorModule.mm} +175 -119
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -371,6 +371,16 @@ npm run android
371
371
 
372
372
  ---
373
373
 
374
+ ## 🔒 Anonymous Telemetry
375
+
376
+ `react-native-inapp-inspector` collects anonymous diagnostic usage telemetry (such as React Native framework version, OS platform, and feature interactions) to help maintainers prioritize features and track stability across architectures (e.g. Hermes vs. JSC, New Architecture vs. Paper).
377
+
378
+ - **Zero PII**: No personal data, user identities, authentication tokens, device names, or network request payloads are ever collected.
379
+ - **Fail-Safe & Non-Blocking**: Runs completely in the background without affecting app performance.
380
+ - **User Consent Dialog**: On initial launch, an in-app consent dialog prompts the developer to allow or decline anonymous telemetry.
381
+
382
+ ---
383
+
374
384
  ## 🤝 Contributing
375
385
 
376
386
  Contributions are welcome! Please check out our [Contributing Guidelines](CONTRIBUTING.md) to get started.
@@ -176,6 +176,29 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
176
176
  val cpuAbi = if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown"
177
177
  map.putString("cpuAbi", cpuAbi)
178
178
 
179
+ // 6. Application Identifiers (Legal & Non-PII)
180
+ try {
181
+ val packageManager = reactContext.packageManager
182
+ val packageName = reactContext.packageName
183
+ val packageInfo = packageManager.getPackageInfo(packageName, 0)
184
+ val appInfo = packageManager.getApplicationInfo(packageName, 0)
185
+ val appName = packageManager.getApplicationLabel(appInfo).toString()
186
+ val appVersion = packageInfo.versionName ?: "unknown"
187
+ val appBuild = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
188
+ packageInfo.longVersionCode.toString()
189
+ } else {
190
+ @Suppress("DEPRECATION")
191
+ packageInfo.versionCode.toString()
192
+ }
193
+ map.putString("appName", appName)
194
+ map.putString("appPackageName", packageName)
195
+ map.putString("appBundleId", packageName)
196
+ map.putString("appVersion", appVersion)
197
+ map.putString("appBuild", appBuild)
198
+ } catch (e: Exception) {
199
+ // Ignore app info fallback
200
+ }
201
+
179
202
  promise.resolve(map)
180
203
  } catch (e: Exception) {
181
204
  promise.reject("DEVICE_METRICS_ERROR", e.message, e)
@@ -44,7 +44,23 @@ const AppHeaderLogo = ({ size = 46, customIcon, }) => {
44
44
  const [loadError, setLoadError] = (0, react_1.useState)(false);
45
45
  // 1. If customIcon is explicitly provided, render it directly edge-to-edge
46
46
  if (customIcon) {
47
- return (<react_native_1.View style={[logoStyles.container, { width: size, height: size }]}>
47
+ if (react_1.default.isValidElement(customIcon)) {
48
+ const clonedIcon = react_1.default.cloneElement(customIcon, {
49
+ size,
50
+ width: size,
51
+ height: size,
52
+ });
53
+ return (<react_native_1.View style={[
54
+ logoStyles.container,
55
+ { width: size, height: size, padding: 0, borderRadius: Math.round(size * 0.28) },
56
+ ]}>
57
+ {clonedIcon}
58
+ </react_native_1.View>);
59
+ }
60
+ return (<react_native_1.View style={[
61
+ logoStyles.container,
62
+ { width: size, height: size, borderRadius: Math.round(size * 0.28) },
63
+ ]}>
48
64
  <react_native_1.Image source={customIcon} style={logoStyles.image} resizeMode="cover"/>
49
65
  </react_native_1.View>);
50
66
  }
@@ -60,7 +76,10 @@ const AppHeaderLogo = ({ size = 46, customIcon, }) => {
60
76
  </react_native_1.View>);
61
77
  }
62
78
  // 3. Fallback: Inspector's Signature Owl Brand Logo
63
- return (<react_native_1.View style={[logoStyles.container, { width: size, height: size }]}>
79
+ return (<react_native_1.View style={[
80
+ logoStyles.container,
81
+ { width: size, height: size, padding: 0, borderRadius: Math.round(size * 0.28) },
82
+ ]}>
64
83
  <BrandSquareIcon_1.default size={size}/>
65
84
  </react_native_1.View>);
66
85
  };
@@ -48,9 +48,35 @@ const AppFonts_1 = require("../../styles/AppFonts");
48
48
  const constants_1 = require("../../constants");
49
49
  const constants_2 = require("../../constants");
50
50
  const helpers_1 = require("../../helpers");
51
+ const telemetry_1 = require("../../helpers/telemetry");
51
52
  const NetworkIcons_1 = require("../NetworkIcons");
52
53
  const InspectorHeader = react_1.default.memo(() => {
53
- const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, settingsPage, setSettingsPage, resetToDefaults, closeModal, detailTitle, activeTab, environment, selectedCrash, setSelectedCrash, } = (0, InspectorContext_1.useInspector)();
54
+ const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, settingsPage, setSettingsPage, resetToDefaults, closeModal, detailTitle, activeTab, environment, visible, selectedCrash, setSelectedCrash, } = (0, InspectorContext_1.useInspector)();
55
+ const [isTelemetryActive, setIsTelemetryActive] = react_1.default.useState(false);
56
+ const telemetryPulseAnim = react_1.default.useRef(new react_native_1.Animated.Value(1)).current;
57
+ react_1.default.useEffect(() => {
58
+ (0, telemetry_1.getTelemetryConsentStatus)().then(status => {
59
+ setIsTelemetryActive(status === 'granted');
60
+ });
61
+ }, [visible]);
62
+ react_1.default.useEffect(() => {
63
+ if (!isTelemetryActive)
64
+ return;
65
+ const pulse = react_native_1.Animated.loop(react_native_1.Animated.sequence([
66
+ react_native_1.Animated.timing(telemetryPulseAnim, {
67
+ toValue: 2.2,
68
+ duration: 1500,
69
+ useNativeDriver: true,
70
+ }),
71
+ react_native_1.Animated.timing(telemetryPulseAnim, {
72
+ toValue: 1,
73
+ duration: 0,
74
+ useNativeDriver: true,
75
+ }),
76
+ ]));
77
+ pulse.start();
78
+ return () => pulse.stop();
79
+ }, [isTelemetryActive, telemetryPulseAnim]);
54
80
  const envConfig = (0, react_1.useMemo)(() => {
55
81
  const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
56
82
  const clean = rawEnv.toUpperCase();
@@ -213,7 +239,7 @@ const InspectorHeader = react_1.default.memo(() => {
213
239
  minWidth: 0,
214
240
  marginRight: 6,
215
241
  }}>
216
- <AppHeaderLogo_1.default size={30} customIcon={appIcon}/>
242
+ <AppHeaderLogo_1.default size={46} customIcon={appIcon}/>
217
243
  <react_native_1.View style={{ gap: 2.5, flex: 1, minWidth: 0 }}>
218
244
  <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, minWidth: 0 }}>
219
245
  <react_native_1.Text style={[styles_1.default.headerTitle, { flexShrink: 1 }]} numberOfLines={1} ellipsizeMode="tail">
@@ -260,11 +286,11 @@ const InspectorHeader = react_1.default.memo(() => {
260
286
  </react_native_1.Pressable>)}
261
287
  </react_native_1.View>
262
288
 
263
- {/* Clean Bundle Identifier & Version Representation */}
289
+ {/* OS & NPM Version Representation */}
264
290
  <react_native_1.View style={{
265
291
  flexDirection: 'row',
266
292
  alignItems: 'center',
267
- gap: 4.5,
293
+ gap: 5,
268
294
  minWidth: 0,
269
295
  }}>
270
296
  <react_native_1.View style={{
@@ -274,21 +300,21 @@ const InspectorHeader = react_1.default.memo(() => {
274
300
  borderRadius: 5,
275
301
  paddingHorizontal: 6,
276
302
  paddingVertical: 2,
277
- gap: 3.5,
303
+ gap: 4,
278
304
  borderWidth: 1,
279
305
  borderColor: `${AppColors_1.AppColors.white}2E`,
280
- flexShrink: 1,
281
- minWidth: 0,
306
+ flexShrink: 0,
282
307
  }}>
283
- {react_native_1.Platform.OS === 'ios' ? (<NetworkIcons_1.AppleIcon color={`${AppColors_1.AppColors.white}D9`} size={9}/>) : (<NetworkIcons_1.AndroidIcon color={`${AppColors_1.AppColors.white}D9`} size={9}/>)}
308
+ {react_native_1.Platform.OS === 'ios' ? (<NetworkIcons_1.AppleIcon color={`${AppColors_1.AppColors.white}E6`} size={10}/>) : (<NetworkIcons_1.AndroidIcon color={`${AppColors_1.AppColors.white}E6`} size={10}/>)}
284
309
  <react_native_1.Text style={{
285
310
  fontFamily: AppFonts_1.AppFonts.interMedium,
286
311
  fontSize: 9.5,
287
312
  color: `${AppColors_1.AppColors.white}EB`,
288
313
  letterSpacing: 0.1,
289
- flexShrink: 1,
290
- }} numberOfLines={1} ellipsizeMode="middle">
291
- {(0, helpers_1.getBundleIdentifier)()}
314
+ }} numberOfLines={1}>
315
+ {react_native_1.Platform.OS === 'ios'
316
+ ? `iOS ${react_native_1.Platform.Version}`
317
+ : `Android ${react_native_1.Platform.Version}`}
292
318
  </react_native_1.Text>
293
319
  </react_native_1.View>
294
320
 
@@ -317,6 +343,53 @@ const InspectorHeader = react_1.default.memo(() => {
317
343
  v{constants_2.LIB_VERSION}
318
344
  </react_native_1.Text>
319
345
  </react_native_1.Pressable>
346
+
347
+ {isTelemetryActive && (<react_native_1.Pressable onPress={() => react_native_1.Alert.alert('Anonymous Telemetry Active', 'Anonymous diagnostics (such as React Native version, JavaScript engine, and device model) are currently enabled to help improve library tooling.\n\nNo user data or network payloads are captured. You can toggle this anytime in Settings.', [{ text: 'Got it' }])} style={{
348
+ flexDirection: 'row',
349
+ alignItems: 'center',
350
+ backgroundColor: 'rgba(16, 185, 129, 0.22)',
351
+ borderRadius: 5,
352
+ paddingHorizontal: 5.5,
353
+ paddingVertical: 2,
354
+ gap: 4.5,
355
+ borderWidth: 1,
356
+ borderColor: 'rgba(52, 211, 153, 0.45)',
357
+ flexShrink: 0,
358
+ }}>
359
+ <react_native_1.View style={{
360
+ width: 6,
361
+ height: 6,
362
+ alignItems: 'center',
363
+ justifyContent: 'center',
364
+ }}>
365
+ <react_native_1.Animated.View style={{
366
+ position: 'absolute',
367
+ width: 6,
368
+ height: 6,
369
+ borderRadius: 3,
370
+ backgroundColor: '#34D399',
371
+ opacity: telemetryPulseAnim.interpolate({
372
+ inputRange: [1, 2.2],
373
+ outputRange: [0.8, 0],
374
+ }),
375
+ transform: [{ scale: telemetryPulseAnim }],
376
+ }}/>
377
+ <react_native_1.View style={{
378
+ width: 4,
379
+ height: 4,
380
+ borderRadius: 2,
381
+ backgroundColor: '#10B981',
382
+ }}/>
383
+ </react_native_1.View>
384
+ <react_native_1.Text style={{
385
+ fontFamily: AppFonts_1.AppFonts.interBold,
386
+ fontSize: 9,
387
+ color: '#6EE7B7',
388
+ letterSpacing: 0.2,
389
+ }}>
390
+ LIVE
391
+ </react_native_1.Text>
392
+ </react_native_1.Pressable>)}
320
393
  </react_native_1.View>
321
394
  </react_native_1.View>
322
395
  </react_native_1.View>) : null}
@@ -56,6 +56,8 @@ const PerformanceTab_1 = __importDefault(require("./PerformanceTab"));
56
56
  const CrashTab_1 = __importDefault(require("./CrashTab"));
57
57
  const CrashDetail_1 = __importDefault(require("./CrashDetail"));
58
58
  const SettingsPanel_1 = __importDefault(require("./SettingsPanel"));
59
+ const TelemetryConsentModal_1 = __importDefault(require("./TelemetryConsentModal"));
60
+ const NpmUpdateToast_1 = __importDefault(require("./NpmUpdateToast"));
59
61
  const Toast_1 = __importDefault(require("../Toast"));
60
62
  const styles_1 = __importDefault(require("../../styles"));
61
63
  const AppColors_1 = require("../../styles/AppColors");
@@ -204,11 +206,17 @@ const MainScreen = () => {
204
206
 
205
207
  {/* Bottom floating toast notification */}
206
208
  <Toast_1.default />
209
+
210
+ {/* NPM Version Update Toast with timeout progress bar */}
211
+ <NpmUpdateToast_1.default />
207
212
  </react_native_1.View>
208
213
  </react_native_1.View>
209
214
  </ErrorBoundary_1.default>)}
210
215
  {hasNavigationContext && (<NavigationTracker_1.default onStateChange={setNavState}/>)}
211
216
  </react_native_1.Modal>
217
+
218
+ {/* Anonymous Telemetry Consent Dialog at root viewport */}
219
+ <TelemetryConsentModal_1.default />
212
220
  </>);
213
221
  };
214
222
  exports.default = MainScreen;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare const NpmUpdateToast: () => React.JSX.Element;
3
+ export default NpmUpdateToast;
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NpmUpdateToast = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const react_native_svg_1 = __importStar(require("react-native-svg"));
40
+ const InspectorContext_1 = require("./InspectorContext");
41
+ const constants_1 = require("../../constants");
42
+ const NetworkIcons_1 = require("../NetworkIcons");
43
+ const CloseSvg = ({ size = 13, color = '#94A3B8' }) => (<react_native_svg_1.default width={size} height={size} viewBox="0 0 24 24" fill="none">
44
+ <react_native_svg_1.Path d="M18 6L6 18M6 6l12 12" stroke={color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"/>
45
+ </react_native_svg_1.default>);
46
+ const ExternalLinkSvg = ({ size = 12, color = '#FFFFFF' }) => (<react_native_svg_1.default width={size} height={size} viewBox="0 0 24 24" fill="none">
47
+ <react_native_svg_1.Path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
48
+ <react_native_svg_1.Path d="M15 3h6v6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
49
+ <react_native_svg_1.Path d="M10 14L21 3" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
50
+ </react_native_svg_1.default>);
51
+ const TOAST_TIMEOUT_MS = 6500;
52
+ const NpmUpdateToast = () => {
53
+ const { updateAvailable, latestNpmVersion, showUpdateToast } = (0, InspectorContext_1.useInspector)();
54
+ const [dismissed, setDismissed] = (0, react_1.useState)(false);
55
+ const [visible, setVisible] = (0, react_1.useState)(false);
56
+ const translateYAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(50)).current;
57
+ const opacityAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
58
+ const progressAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
59
+ const timerRef = (0, react_1.useRef)(null);
60
+ (0, react_1.useEffect)(() => {
61
+ if (showUpdateToast && updateAvailable && latestNpmVersion && !dismissed) {
62
+ setVisible(true);
63
+ // Slide and fade in
64
+ react_native_1.Animated.parallel([
65
+ react_native_1.Animated.spring(translateYAnim, {
66
+ toValue: 0,
67
+ friction: 8,
68
+ tension: 40,
69
+ useNativeDriver: true,
70
+ }),
71
+ react_native_1.Animated.timing(opacityAnim, {
72
+ toValue: 1,
73
+ duration: 250,
74
+ useNativeDriver: true,
75
+ }),
76
+ ]).start();
77
+ // Progress bar countdown from 1 to 0
78
+ progressAnim.setValue(1);
79
+ react_native_1.Animated.timing(progressAnim, {
80
+ toValue: 0,
81
+ duration: TOAST_TIMEOUT_MS,
82
+ useNativeDriver: false,
83
+ }).start();
84
+ // Auto-hide after timeout
85
+ timerRef.current = setTimeout(() => {
86
+ handleDismiss();
87
+ }, TOAST_TIMEOUT_MS);
88
+ }
89
+ return () => {
90
+ if (timerRef.current) {
91
+ clearTimeout(timerRef.current);
92
+ }
93
+ };
94
+ }, [showUpdateToast, updateAvailable, latestNpmVersion, dismissed]);
95
+ const handleDismiss = () => {
96
+ react_native_1.Animated.parallel([
97
+ react_native_1.Animated.timing(opacityAnim, {
98
+ toValue: 0,
99
+ duration: 200,
100
+ useNativeDriver: true,
101
+ }),
102
+ react_native_1.Animated.timing(translateYAnim, {
103
+ toValue: 50,
104
+ duration: 200,
105
+ useNativeDriver: true,
106
+ }),
107
+ ]).start(() => {
108
+ setVisible(false);
109
+ setDismissed(true);
110
+ });
111
+ };
112
+ const handleOpenNpm = () => {
113
+ react_native_1.Linking.openURL('https://www.npmjs.com/package/react-native-inapp-inspector').catch(() => { });
114
+ handleDismiss();
115
+ };
116
+ if (!showUpdateToast || !visible || !updateAvailable || !latestNpmVersion)
117
+ return null;
118
+ const progressWidth = progressAnim.interpolate({
119
+ inputRange: [0, 1],
120
+ outputRange: ['0%', '100%'],
121
+ });
122
+ return (<react_native_1.Animated.View style={[
123
+ styles.container,
124
+ {
125
+ opacity: opacityAnim,
126
+ transform: [{ translateY: translateYAnim }],
127
+ },
128
+ ]}>
129
+ <react_native_1.View style={styles.contentRow}>
130
+ {/* Left NPM Badge Icon */}
131
+ <react_native_1.View style={styles.npmIconContainer}>
132
+ <NetworkIcons_1.NpmIcon size={14} color="#CB3837"/>
133
+ </react_native_1.View>
134
+
135
+ {/* Text Details */}
136
+ <react_native_1.View style={styles.textContainer}>
137
+ <react_native_1.View style={styles.titleRow}>
138
+ <react_native_1.Text style={styles.titleText}>Update Available</react_native_1.Text>
139
+ <react_native_1.View style={styles.versionPill}>
140
+ <react_native_1.Text style={styles.versionPillText}>v{latestNpmVersion}</react_native_1.Text>
141
+ </react_native_1.View>
142
+ </react_native_1.View>
143
+ <react_native_1.Text style={styles.subtitleText} numberOfLines={1}>
144
+ Installed: v{constants_1.LIB_VERSION} • New version is ready
145
+ </react_native_1.Text>
146
+ </react_native_1.View>
147
+
148
+ {/* Action Button: View */}
149
+ <react_native_1.TouchableOpacity style={styles.actionButton} onPress={handleOpenNpm} activeOpacity={0.8}>
150
+ <react_native_1.Text style={styles.actionButtonText}>View</react_native_1.Text>
151
+ <ExternalLinkSvg size={10} color="#FFFFFF"/>
152
+ </react_native_1.TouchableOpacity>
153
+
154
+ {/* Close Button */}
155
+ <react_native_1.TouchableOpacity style={styles.closeButton} onPress={handleDismiss} hitSlop={8} activeOpacity={0.7}>
156
+ <CloseSvg size={12} color="#94A3B8"/>
157
+ </react_native_1.TouchableOpacity>
158
+ </react_native_1.View>
159
+
160
+ {/* Animated Linear Timeout Progress Bar */}
161
+ <react_native_1.View style={styles.progressBarTrack}>
162
+ <react_native_1.Animated.View style={[styles.progressBarFill, { width: progressWidth }]}/>
163
+ </react_native_1.View>
164
+ </react_native_1.Animated.View>);
165
+ };
166
+ exports.NpmUpdateToast = NpmUpdateToast;
167
+ const fontStack = react_native_1.Platform.select({
168
+ ios: {
169
+ fontFamily: 'System',
170
+ },
171
+ android: {
172
+ fontFamily: 'sans-serif',
173
+ },
174
+ default: {},
175
+ });
176
+ const styles = react_native_1.StyleSheet.create({
177
+ container: {
178
+ position: 'absolute',
179
+ bottom: 24,
180
+ left: 16,
181
+ right: 16,
182
+ backgroundColor: '#0F172A',
183
+ borderRadius: 16,
184
+ borderWidth: 1,
185
+ borderColor: '#334155',
186
+ shadowColor: '#000000',
187
+ shadowOffset: { width: 0, height: 10 },
188
+ shadowOpacity: 0.35,
189
+ shadowRadius: 20,
190
+ elevation: 20,
191
+ overflow: 'hidden',
192
+ zIndex: 99999,
193
+ },
194
+ contentRow: {
195
+ flexDirection: 'row',
196
+ alignItems: 'center',
197
+ paddingVertical: 12,
198
+ paddingHorizontal: 14,
199
+ gap: 10,
200
+ },
201
+ npmIconContainer: {
202
+ width: 32,
203
+ height: 32,
204
+ borderRadius: 10,
205
+ backgroundColor: '#FFFFFF',
206
+ justifyContent: 'center',
207
+ alignItems: 'center',
208
+ borderWidth: 1,
209
+ borderColor: '#CBD5E1',
210
+ flexShrink: 0,
211
+ },
212
+ textContainer: {
213
+ flex: 1,
214
+ minWidth: 0,
215
+ },
216
+ titleRow: {
217
+ flexDirection: 'row',
218
+ alignItems: 'center',
219
+ gap: 6,
220
+ },
221
+ titleText: {
222
+ fontSize: 13,
223
+ fontWeight: '700',
224
+ color: '#F8FAFC',
225
+ letterSpacing: -0.2,
226
+ ...fontStack,
227
+ },
228
+ versionPill: {
229
+ backgroundColor: '#7C3AED26',
230
+ paddingHorizontal: 6,
231
+ paddingVertical: 1.5,
232
+ borderRadius: 6,
233
+ borderWidth: 1,
234
+ borderColor: '#7C3AED59',
235
+ },
236
+ versionPillText: {
237
+ fontSize: 10.5,
238
+ fontWeight: '700',
239
+ color: '#A78BFA',
240
+ ...fontStack,
241
+ },
242
+ subtitleText: {
243
+ fontSize: 11,
244
+ color: '#94A3B8',
245
+ marginTop: 1.5,
246
+ ...fontStack,
247
+ },
248
+ actionButton: {
249
+ flexDirection: 'row',
250
+ alignItems: 'center',
251
+ gap: 4,
252
+ backgroundColor: '#7C3AED',
253
+ paddingVertical: 6,
254
+ paddingHorizontal: 10,
255
+ borderRadius: 8,
256
+ flexShrink: 0,
257
+ shadowColor: '#7C3AED',
258
+ shadowOffset: { width: 0, height: 2 },
259
+ shadowOpacity: 0.3,
260
+ shadowRadius: 4,
261
+ elevation: 3,
262
+ },
263
+ actionButtonText: {
264
+ fontSize: 11.5,
265
+ fontWeight: '700',
266
+ color: '#FFFFFF',
267
+ ...fontStack,
268
+ },
269
+ closeButton: {
270
+ width: 24,
271
+ height: 24,
272
+ borderRadius: 12,
273
+ backgroundColor: '#1E293B',
274
+ justifyContent: 'center',
275
+ alignItems: 'center',
276
+ flexShrink: 0,
277
+ },
278
+ progressBarTrack: {
279
+ height: 3,
280
+ width: '100%',
281
+ backgroundColor: '#1E293B',
282
+ },
283
+ progressBarFill: {
284
+ height: '100%',
285
+ backgroundColor: '#7C3AED',
286
+ },
287
+ });
288
+ exports.default = exports.NpmUpdateToast;