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.
- package/README.md +10 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +23 -0
- package/dist/commonjs/components/AppHeaderLogo.js +21 -2
- package/dist/commonjs/components/Inspector/InspectorHeader.js +84 -11
- package/dist/commonjs/components/Inspector/MainScreen.js +8 -0
- package/dist/commonjs/components/Inspector/NpmUpdateToast.d.ts +3 -0
- package/dist/commonjs/components/Inspector/NpmUpdateToast.js +288 -0
- package/dist/commonjs/components/Inspector/SettingsPanel.js +192 -1
- package/dist/commonjs/components/Inspector/TelemetryConsentModal.d.ts +3 -0
- package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +359 -0
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/helpers/index.d.ts +1 -0
- package/dist/commonjs/helpers/index.js +1 -0
- package/dist/commonjs/helpers/telemetry.d.ts +99 -0
- package/dist/commonjs/helpers/telemetry.js +398 -0
- package/dist/commonjs/index.d.ts +2 -0
- package/dist/commonjs/index.js +37 -3
- package/dist/commonjs/native/NativeInspector.d.ts +5 -0
- package/dist/commonjs/native/NativeInspector.js +3 -2
- package/dist/commonjs/types/interfaces.d.ts +5 -0
- package/dist/esm/components/AppHeaderLogo.js +21 -2
- package/dist/esm/components/Inspector/InspectorHeader.js +85 -12
- package/dist/esm/components/Inspector/MainScreen.js +8 -0
- package/dist/esm/components/Inspector/NpmUpdateToast.d.ts +3 -0
- package/dist/esm/components/Inspector/NpmUpdateToast.js +251 -0
- package/dist/esm/components/Inspector/SettingsPanel.js +192 -1
- package/dist/esm/components/Inspector/TelemetryConsentModal.d.ts +3 -0
- package/dist/esm/components/Inspector/TelemetryConsentModal.js +322 -0
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +1 -0
- package/dist/esm/helpers/telemetry.d.ts +99 -0
- package/dist/esm/helpers/telemetry.js +380 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +27 -3
- package/dist/esm/native/NativeInspector.d.ts +5 -0
- package/dist/esm/native/NativeInspector.js +3 -2
- package/dist/esm/types/interfaces.d.ts +5 -0
- package/ios/{NetworkInspectorModule.m → NetworkInspectorModule.mm} +175 -119
- 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
|
-
|
|
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={[
|
|
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={
|
|
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
|
-
{/*
|
|
289
|
+
{/* OS & NPM Version Representation */}
|
|
264
290
|
<react_native_1.View style={{
|
|
265
291
|
flexDirection: 'row',
|
|
266
292
|
alignItems: 'center',
|
|
267
|
-
gap:
|
|
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:
|
|
303
|
+
gap: 4,
|
|
278
304
|
borderWidth: 1,
|
|
279
305
|
borderColor: `${AppColors_1.AppColors.white}2E`,
|
|
280
|
-
flexShrink:
|
|
281
|
-
minWidth: 0,
|
|
306
|
+
flexShrink: 0,
|
|
282
307
|
}}>
|
|
283
|
-
{react_native_1.Platform.OS === 'ios' ? (<NetworkIcons_1.AppleIcon color={`${AppColors_1.AppColors.white}
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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,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;
|