react-native-debug-toolkit 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js +77 -20
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/commonjs/features/devConnect/DevConnectTab.js +150 -35
- package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectQrScanner.js +78 -21
- package/lib/module/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +151 -36
- package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/devConnect/DevConnectQrScanner.tsx +71 -20
- package/src/features/devConnect/DevConnectTab.tsx +100 -20
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { Component, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Modal,
|
|
4
4
|
Pressable,
|
|
@@ -16,6 +16,37 @@ import {
|
|
|
16
16
|
} from './cameraKit';
|
|
17
17
|
import { parseMetroQrPayload } from './devConnectUtils';
|
|
18
18
|
|
|
19
|
+
// ─── Camera Error Boundary ─────────────────────────────────
|
|
20
|
+
|
|
21
|
+
interface CameraBoundaryProps {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
onCameraError: (msg: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CameraBoundaryState {
|
|
27
|
+
hasError: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class CameraErrorBoundary extends Component<CameraBoundaryProps, CameraBoundaryState> {
|
|
31
|
+
state: CameraBoundaryState = { hasError: false };
|
|
32
|
+
|
|
33
|
+
static getDerivedStateFromError(): CameraBoundaryState {
|
|
34
|
+
return { hasError: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
componentDidCatch(error: Error) {
|
|
38
|
+
console.warn('[DevConnect] Camera error:', error.message);
|
|
39
|
+
this.props.onCameraError(error.message || 'Camera failed to initialize.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render() {
|
|
43
|
+
if (this.state.hasError) return null;
|
|
44
|
+
return this.props.children;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── QR Scanner ─────────────────────────────────────────────
|
|
49
|
+
|
|
19
50
|
interface DevConnectQrScannerProps {
|
|
20
51
|
visible: boolean;
|
|
21
52
|
onClose: () => void;
|
|
@@ -25,12 +56,14 @@ interface DevConnectQrScannerProps {
|
|
|
25
56
|
export function DevConnectQrScanner({ visible, onClose, onScanHost }: DevConnectQrScannerProps) {
|
|
26
57
|
const scannedRef = useRef(false);
|
|
27
58
|
const [error, setError] = useState<string | null>(null);
|
|
59
|
+
const [cameraFailed, setCameraFailed] = useState(false);
|
|
28
60
|
const scanner = getScannerModule();
|
|
29
61
|
|
|
30
62
|
useEffect(() => {
|
|
31
63
|
if (visible) {
|
|
32
64
|
scannedRef.current = false;
|
|
33
65
|
setError(null);
|
|
66
|
+
setCameraFailed(false);
|
|
34
67
|
}
|
|
35
68
|
}, [visible]);
|
|
36
69
|
|
|
@@ -58,31 +91,46 @@ export function DevConnectQrScanner({ visible, onClose, onScanHost }: DevConnect
|
|
|
58
91
|
handleScanned(result.value ?? '');
|
|
59
92
|
}, [handleScanned]);
|
|
60
93
|
|
|
94
|
+
const handleCameraError = useCallback((_msg: string) => {
|
|
95
|
+
setCameraFailed(true);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
61
98
|
if (!visible || !scanner) return null;
|
|
62
99
|
|
|
63
100
|
return (
|
|
64
101
|
<Modal visible={visible} animationType="slide" onRequestClose={onClose}>
|
|
65
102
|
<View style={styles.container}>
|
|
66
|
-
{
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
{!cameraFailed && (
|
|
104
|
+
<CameraErrorBoundary onCameraError={handleCameraError}>
|
|
105
|
+
{scanner.kind === 'camera-kit' && scanner.CameraKit ? (
|
|
106
|
+
<scanner.CameraKit.Camera
|
|
107
|
+
style={styles.camera}
|
|
108
|
+
cameraType={scanner.CameraKit.CameraType?.Back}
|
|
109
|
+
scanBarcode
|
|
110
|
+
onReadCode={handleCameraKitRead}
|
|
111
|
+
showFrame
|
|
112
|
+
laserColor={Colors.primary}
|
|
113
|
+
frameColor={Colors.primary}
|
|
114
|
+
allowedBarcodeTypes={['qr']}
|
|
115
|
+
/>
|
|
116
|
+
) : scanner.kind === 'expo-camera' && scanner.ExpoCamera ? (
|
|
117
|
+
<scanner.ExpoCamera.Camera
|
|
118
|
+
style={styles.camera}
|
|
119
|
+
onBarCodeScanned={handleExpoScanned}
|
|
120
|
+
barCodeScannerSettings={{ barCodeTypes: ['qr'] }}
|
|
121
|
+
/>
|
|
122
|
+
) : null}
|
|
123
|
+
</CameraErrorBoundary>
|
|
124
|
+
)}
|
|
125
|
+
{cameraFailed && (
|
|
126
|
+
<View style={styles.cameraFallback}>
|
|
127
|
+
<Text style={styles.cameraFallbackText}>Camera unavailable.</Text>
|
|
128
|
+
<Text style={styles.cameraFallbackHint}>Please enter computer IP manually.</Text>
|
|
129
|
+
</View>
|
|
130
|
+
)}
|
|
84
131
|
<View style={styles.footer}>
|
|
85
|
-
{
|
|
132
|
+
{!cameraFailed && !error && <Text style={styles.hint}>Scan a Metro QR code.</Text>}
|
|
133
|
+
{error && <Text style={styles.error}>{error}</Text>}
|
|
86
134
|
<TouchableOpacity style={styles.closeButton} onPress={onClose} activeOpacity={0.7}>
|
|
87
135
|
<Text style={styles.closeButtonText}>Close</Text>
|
|
88
136
|
</TouchableOpacity>
|
|
@@ -98,6 +146,9 @@ export function DevConnectQrScanner({ visible, onClose, onScanHost }: DevConnect
|
|
|
98
146
|
const styles = StyleSheet.create({
|
|
99
147
|
container: { flex: 1, backgroundColor: '#000' },
|
|
100
148
|
camera: { flex: 1 },
|
|
149
|
+
cameraFallback: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 },
|
|
150
|
+
cameraFallbackText: { fontSize: 16, color: '#fff', fontWeight: '600', marginBottom: 8 },
|
|
151
|
+
cameraFallbackHint: { fontSize: 13, color: 'rgba(255,255,255,0.6)', textAlign: 'center' },
|
|
101
152
|
footer: { padding: 16, backgroundColor: Colors.surface },
|
|
102
153
|
hint: { fontSize: 13, color: Colors.textSecondary, marginBottom: 12 },
|
|
103
154
|
error: { fontSize: 13, color: Colors.error, marginBottom: 12 },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
KeyboardAvoidingView,
|
|
4
4
|
Platform,
|
|
@@ -47,9 +47,12 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
47
47
|
setSyncState(snapshot.streaming ? 'running' : 'idle');
|
|
48
48
|
}, [snapshot.computerHost, snapshot.streaming]);
|
|
49
49
|
|
|
50
|
-
const metroUrls =
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const metroUrls = useMemo(
|
|
51
|
+
() => isSim
|
|
52
|
+
? { expUrl: `exp://localhost:${METRO_PORT}`, httpUrl: `http://localhost:${METRO_PORT}` }
|
|
53
|
+
: buildMetroUrls(computerHost),
|
|
54
|
+
[isSim, computerHost],
|
|
55
|
+
);
|
|
53
56
|
|
|
54
57
|
const handleHostChange = useCallback((value: string) => {
|
|
55
58
|
setComputerHost(value);
|
|
@@ -174,12 +177,17 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
174
177
|
}
|
|
175
178
|
}, [configureDaemon, validateSettings]);
|
|
176
179
|
|
|
180
|
+
const messageTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
|
181
|
+
|
|
177
182
|
const copyUrl = useCallback((label: string, url: string) => {
|
|
178
183
|
copyToComputer(url, { label });
|
|
179
184
|
setMessage('Copied to computer output.');
|
|
180
|
-
|
|
185
|
+
clearTimeout(messageTimerRef.current);
|
|
186
|
+
messageTimerRef.current = setTimeout(() => setMessage(null), 1500);
|
|
181
187
|
}, []);
|
|
182
188
|
|
|
189
|
+
useEffect(() => () => clearTimeout(messageTimerRef.current), []);
|
|
190
|
+
|
|
183
191
|
const canConnect = isSim || Boolean(normalizeComputerHost(computerHost));
|
|
184
192
|
const busy = sending || syncState === 'checking';
|
|
185
193
|
|
|
@@ -244,24 +252,60 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
244
252
|
{message ? <Text style={styles.message}>{message}</Text> : null}
|
|
245
253
|
|
|
246
254
|
<View style={styles.section}>
|
|
247
|
-
<Text style={styles.sectionTitle}>
|
|
248
|
-
{
|
|
255
|
+
<Text style={styles.sectionTitle}>Remote JS Bundle</Text>
|
|
256
|
+
<Text style={styles.sectionDesc}>
|
|
257
|
+
Load JavaScript from your computer instead of the bundled file. Requires app restart.
|
|
258
|
+
</Text>
|
|
259
|
+
|
|
260
|
+
{!metroUrls ? (
|
|
261
|
+
<View style={styles.stepCard}>
|
|
262
|
+
<Text style={styles.stepHint}>Enter your computer IP above to get started.</Text>
|
|
263
|
+
</View>
|
|
264
|
+
) : (
|
|
249
265
|
<>
|
|
250
|
-
<View style={styles.
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
<Text style={styles.
|
|
254
|
-
</
|
|
266
|
+
<View style={styles.stepCard}>
|
|
267
|
+
<View style={styles.stepHeader}>
|
|
268
|
+
<Text style={styles.stepNumber}>1</Text>
|
|
269
|
+
<Text style={styles.stepTitle}>Copy bundle URL</Text>
|
|
270
|
+
</View>
|
|
271
|
+
<Text style={styles.stepDesc}>Use this URL as your remote JS bundle location:</Text>
|
|
272
|
+
<View style={styles.urlRow}>
|
|
273
|
+
<Text style={styles.urlText} numberOfLines={1}>{metroUrls.httpUrl}</Text>
|
|
274
|
+
<TouchableOpacity style={styles.copyButton} onPress={() => copyUrl('Metro URL', metroUrls.httpUrl)} activeOpacity={0.7}>
|
|
275
|
+
<Text style={styles.copyButtonText}>Copy</Text>
|
|
276
|
+
</TouchableOpacity>
|
|
277
|
+
</View>
|
|
278
|
+
<View style={styles.urlRow}>
|
|
279
|
+
<Text style={styles.urlLabel}>Expo</Text>
|
|
280
|
+
<Text style={styles.urlText} numberOfLines={1}>{metroUrls.expUrl}</Text>
|
|
281
|
+
<TouchableOpacity style={styles.copyButton} onPress={() => copyUrl('Expo URL', metroUrls.expUrl)} activeOpacity={0.7}>
|
|
282
|
+
<Text style={styles.copyButtonText}>Copy</Text>
|
|
283
|
+
</TouchableOpacity>
|
|
284
|
+
</View>
|
|
255
285
|
</View>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<
|
|
259
|
-
<Text style={styles.
|
|
260
|
-
|
|
286
|
+
|
|
287
|
+
<View style={styles.stepCard}>
|
|
288
|
+
<View style={styles.stepHeader}>
|
|
289
|
+
<Text style={styles.stepNumber}>2</Text>
|
|
290
|
+
<Text style={styles.stepTitle}>Configure remote debugging</Text>
|
|
291
|
+
</View>
|
|
292
|
+
<Text style={styles.stepDesc}>
|
|
293
|
+
{isSim
|
|
294
|
+
? 'Simulator uses localhost automatically. Enable remote debugging in Dev Menu.'
|
|
295
|
+
: 'In Dev Menu, set the bundle URL to the copied address.'}
|
|
296
|
+
</Text>
|
|
297
|
+
</View>
|
|
298
|
+
|
|
299
|
+
<View style={styles.stepCard}>
|
|
300
|
+
<View style={styles.stepHeader}>
|
|
301
|
+
<Text style={styles.stepNumber}>3</Text>
|
|
302
|
+
<Text style={styles.stepTitle}>Restart the app</Text>
|
|
303
|
+
</View>
|
|
304
|
+
<Text style={styles.stepDesc}>
|
|
305
|
+
Close and reopen the app to load from Metro. Make sure Metro is running on your computer.
|
|
306
|
+
</Text>
|
|
261
307
|
</View>
|
|
262
308
|
</>
|
|
263
|
-
) : (
|
|
264
|
-
<Text style={styles.hint}>Enter a computer IP to show Metro URLs.</Text>
|
|
265
309
|
)}
|
|
266
310
|
</View>
|
|
267
311
|
</ScrollView>
|
|
@@ -288,7 +332,8 @@ const styles = StyleSheet.create({
|
|
|
288
332
|
},
|
|
289
333
|
badgeText: { fontSize: 13, fontWeight: '500', color: Colors.primary },
|
|
290
334
|
section: { marginBottom: 14 },
|
|
291
|
-
sectionTitle: { fontSize: 14, fontWeight: '600', color: Colors.text, marginBottom:
|
|
335
|
+
sectionTitle: { fontSize: 14, fontWeight: '600', color: Colors.text, marginBottom: 4 },
|
|
336
|
+
sectionDesc: { fontSize: 12, color: Colors.textSecondary, marginBottom: 10, lineHeight: 17 },
|
|
292
337
|
label: { fontSize: 13, fontWeight: '500', color: Colors.textSecondary, marginBottom: 6 },
|
|
293
338
|
inputRow: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
|
294
339
|
input: {
|
|
@@ -339,6 +384,41 @@ const styles = StyleSheet.create({
|
|
|
339
384
|
buttonDisabled: { opacity: 0.5 },
|
|
340
385
|
message: { fontSize: 12, lineHeight: 17, color: Colors.textSecondary, marginBottom: 12 },
|
|
341
386
|
hint: { fontSize: 12, color: Colors.textLight },
|
|
387
|
+
stepCard: {
|
|
388
|
+
backgroundColor: Colors.surface,
|
|
389
|
+
borderWidth: 1,
|
|
390
|
+
borderColor: Colors.border,
|
|
391
|
+
borderRadius: 10,
|
|
392
|
+
padding: 12,
|
|
393
|
+
marginBottom: 8,
|
|
394
|
+
},
|
|
395
|
+
stepHint: { fontSize: 12, color: Colors.textSecondary, lineHeight: 17 },
|
|
396
|
+
stepHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 },
|
|
397
|
+
stepNumber: {
|
|
398
|
+
width: 20,
|
|
399
|
+
height: 20,
|
|
400
|
+
borderRadius: 10,
|
|
401
|
+
backgroundColor: Colors.primary,
|
|
402
|
+
color: '#fff',
|
|
403
|
+
fontSize: 11,
|
|
404
|
+
fontWeight: '700',
|
|
405
|
+
textAlign: 'center',
|
|
406
|
+
lineHeight: 20,
|
|
407
|
+
marginRight: 8,
|
|
408
|
+
overflow: 'hidden',
|
|
409
|
+
},
|
|
410
|
+
stepTitle: { fontSize: 13, fontWeight: '600', color: Colors.text },
|
|
411
|
+
stepDesc: { fontSize: 12, color: Colors.textSecondary, lineHeight: 17, marginBottom: 8 },
|
|
412
|
+
urlLabel: {
|
|
413
|
+
fontSize: 10,
|
|
414
|
+
fontWeight: '600',
|
|
415
|
+
color: Colors.primary,
|
|
416
|
+
backgroundColor: `${Colors.primary}15`,
|
|
417
|
+
paddingHorizontal: 6,
|
|
418
|
+
paddingVertical: 2,
|
|
419
|
+
borderRadius: 4,
|
|
420
|
+
marginRight: 6,
|
|
421
|
+
},
|
|
342
422
|
urlRow: {
|
|
343
423
|
flexDirection: 'row',
|
|
344
424
|
alignItems: 'center',
|