react-native-debug-toolkit 3.2.1 → 3.2.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.
- package/README.md +13 -2
- package/README.zh-CN.md +13 -2
- package/android/build.gradle +34 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitDevConnectModule.java +70 -0
- package/android/src/main/java/com/reactnativedebugtoolkit/ReactNativeDebugToolkitPackage.java +25 -0
- package/ios/DebugToolkitDevConnect.mm +67 -0
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js +18 -7
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/commonjs/features/devConnect/DevConnectTab.js +232 -161
- package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectPreferences.js +35 -5
- package/lib/commonjs/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectUtils.js +99 -15
- package/lib/commonjs/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/commonjs/features/devConnect/index.js +39 -2
- package/lib/commonjs/features/devConnect/index.js.map +1 -1
- package/lib/commonjs/features/devConnect/nativeDevConnect.js +110 -0
- package/lib/commonjs/features/devConnect/nativeDevConnect.js.map +1 -0
- package/lib/commonjs/features/devConnect/platformDetect.js +7 -11
- package/lib/commonjs/features/devConnect/platformDetect.js.map +1 -1
- package/lib/commonjs/utils/debugPreferences.js +43 -6
- package/lib/commonjs/utils/debugPreferences.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectQrScanner.js +18 -7
- package/lib/module/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +235 -164
- package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/module/features/devConnect/devConnectPreferences.js +33 -6
- package/lib/module/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/module/features/devConnect/devConnectUtils.js +94 -15
- package/lib/module/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/module/features/devConnect/index.js +11 -3
- package/lib/module/features/devConnect/index.js.map +1 -1
- package/lib/module/features/devConnect/nativeDevConnect.js +104 -0
- package/lib/module/features/devConnect/nativeDevConnect.js.map +1 -0
- package/lib/module/features/devConnect/platformDetect.js +8 -12
- package/lib/module/features/devConnect/platformDetect.js.map +1 -1
- package/lib/module/utils/debugPreferences.js +43 -6
- package/lib/module/utils/debugPreferences.js.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts +3 -2
- 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/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts +6 -0
- package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts +18 -1
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/index.d.ts +2 -2
- package/lib/typescript/src/features/devConnect/index.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts +17 -0
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts.map +1 -0
- package/lib/typescript/src/features/devConnect/platformDetect.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/types.d.ts +3 -0
- package/lib/typescript/src/features/devConnect/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/debugPreferences.d.ts +2 -0
- package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
- package/package.json +4 -1
- package/react-native-debug-toolkit.podspec +18 -0
- package/src/features/devConnect/DevConnectQrScanner.tsx +20 -9
- package/src/features/devConnect/DevConnectTab.tsx +227 -105
- package/src/features/devConnect/devConnectPreferences.ts +50 -5
- package/src/features/devConnect/devConnectUtils.ts +122 -15
- package/src/features/devConnect/index.ts +13 -0
- package/src/features/devConnect/nativeDevConnect.ts +128 -0
- package/src/features/devConnect/platformDetect.ts +8 -13
- package/src/features/devConnect/types.ts +3 -0
- package/src/utils/debugPreferences.ts +49 -4
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import type { DebugFeatureRenderProps } from '../../types';
|
|
14
14
|
import { Colors } from '../../ui/theme/colors';
|
|
15
|
-
import { copyToComputer } from '../../utils/copyToComputer';
|
|
16
15
|
import {
|
|
17
16
|
buildDeviceDaemonEndpoint,
|
|
18
17
|
daemonClient,
|
|
@@ -20,53 +19,115 @@ import {
|
|
|
20
19
|
normalizeDaemonSettings,
|
|
21
20
|
type DaemonSettings,
|
|
22
21
|
} from '../../utils/DaemonClient';
|
|
23
|
-
import {
|
|
24
|
-
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_DAEMON_PORT,
|
|
24
|
+
DEFAULT_METRO_PORT,
|
|
25
|
+
buildDaemonDeviceHost,
|
|
26
|
+
buildMetroTarget,
|
|
27
|
+
buildMetroUrls,
|
|
28
|
+
normalizeComputerHost,
|
|
29
|
+
normalizePort,
|
|
30
|
+
parseComputerTarget,
|
|
31
|
+
type ParsedComputerTarget,
|
|
32
|
+
} from './devConnectUtils';
|
|
33
|
+
import {
|
|
34
|
+
saveComputerTarget,
|
|
35
|
+
saveDaemonPort,
|
|
36
|
+
saveMetroPort,
|
|
37
|
+
} from './devConnectPreferences';
|
|
38
|
+
import { applyMetroBundle, resetMetroBundle } from './nativeDevConnect';
|
|
25
39
|
import type { DevConnectState } from './types';
|
|
26
40
|
import { DevConnectQrScanner } from './DevConnectQrScanner';
|
|
27
41
|
|
|
28
42
|
const CONNECTION_TIMEOUT_MS = 2000;
|
|
29
|
-
const METRO_PORT = '8081';
|
|
30
43
|
|
|
31
44
|
type SyncUiState = 'idle' | 'checking' | 'connected' | 'retrying' | 'failed' | 'running';
|
|
32
45
|
|
|
46
|
+
function getSimulatorMetroHost(): string {
|
|
47
|
+
return Platform.OS === 'android' ? '10.0.2.2' : 'localhost';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function describeMetroFailure(result: { reason: string; error?: string }): string {
|
|
51
|
+
if (result.reason === 'native_unavailable') {
|
|
52
|
+
return 'Native DevConnect not installed. Rebuild app after installing native module.';
|
|
53
|
+
}
|
|
54
|
+
if (result.reason === 'metro_unreachable') {
|
|
55
|
+
return result.error ? `Metro not reachable: ${result.error}` : 'Metro not reachable. Start Metro on that port.';
|
|
56
|
+
}
|
|
57
|
+
if (result.reason === 'fetch_unavailable') {
|
|
58
|
+
return 'Cannot check Metro because fetch is unavailable.';
|
|
59
|
+
}
|
|
60
|
+
if (result.reason === 'invalid_target') {
|
|
61
|
+
return 'Enter a valid computer IP and Metro port.';
|
|
62
|
+
}
|
|
63
|
+
return result.error ? `Metro switch failed: ${result.error}` : 'Metro switch failed.';
|
|
64
|
+
}
|
|
65
|
+
|
|
33
66
|
export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectState>) {
|
|
34
67
|
const inputRef = useRef<TextInput>(null);
|
|
35
68
|
const [computerHost, setComputerHost] = useState(snapshot.computerHost);
|
|
69
|
+
const [metroPort, setMetroPort] = useState(snapshot.metroPort);
|
|
70
|
+
const [daemonPort, setDaemonPort] = useState(snapshot.daemonPort);
|
|
36
71
|
const [streaming, setStreaming] = useState(snapshot.streaming);
|
|
37
72
|
const [syncState, setSyncState] = useState<SyncUiState>(snapshot.streaming ? 'running' : 'idle');
|
|
38
73
|
const [message, setMessage] = useState<string | null>(null);
|
|
39
74
|
const [sending, setSending] = useState(false);
|
|
75
|
+
const [metroBusy, setMetroBusy] = useState(false);
|
|
40
76
|
const [qrVisible, setQrVisible] = useState(false);
|
|
41
77
|
|
|
42
78
|
const isSim = snapshot.isSimulator;
|
|
43
79
|
|
|
44
80
|
useEffect(() => {
|
|
45
81
|
setComputerHost(snapshot.computerHost);
|
|
82
|
+
setMetroPort(snapshot.metroPort);
|
|
83
|
+
setDaemonPort(snapshot.daemonPort);
|
|
46
84
|
setStreaming(snapshot.streaming);
|
|
47
85
|
setSyncState(snapshot.streaming ? 'running' : 'idle');
|
|
48
|
-
}, [snapshot.computerHost, snapshot.streaming]);
|
|
86
|
+
}, [snapshot.computerHost, snapshot.daemonPort, snapshot.metroPort, snapshot.streaming]);
|
|
49
87
|
|
|
88
|
+
const metroHost = isSim ? getSimulatorMetroHost() : computerHost;
|
|
89
|
+
const metroTarget = useMemo(
|
|
90
|
+
() => buildMetroTarget(metroHost, metroPort),
|
|
91
|
+
[metroHost, metroPort],
|
|
92
|
+
);
|
|
50
93
|
const metroUrls = useMemo(
|
|
51
|
-
() =>
|
|
52
|
-
|
|
53
|
-
: buildMetroUrls(computerHost),
|
|
54
|
-
[isSim, computerHost],
|
|
94
|
+
() => buildMetroUrls(metroHost, metroPort),
|
|
95
|
+
[metroHost, metroPort],
|
|
55
96
|
);
|
|
56
97
|
|
|
57
98
|
const handleHostChange = useCallback((value: string) => {
|
|
58
99
|
setComputerHost(value);
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
100
|
+
const target = parseComputerTarget(value);
|
|
101
|
+
if (target) {
|
|
102
|
+
setMetroPort(target.metroPort);
|
|
103
|
+
saveComputerTarget(value).catch(() => {});
|
|
62
104
|
}
|
|
63
105
|
setSyncState((prev) => (prev === 'failed' ? 'idle' : prev));
|
|
64
106
|
setMessage(null);
|
|
65
107
|
}, []);
|
|
66
108
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
const handleMetroPortChange = useCallback((value: string) => {
|
|
110
|
+
setMetroPort(value);
|
|
111
|
+
const normalized = normalizePort(value);
|
|
112
|
+
if (normalized) {
|
|
113
|
+
saveMetroPort(normalized).catch(() => {});
|
|
114
|
+
}
|
|
115
|
+
setMessage(null);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
const handleDaemonPortChange = useCallback((value: string) => {
|
|
119
|
+
setDaemonPort(value);
|
|
120
|
+
const normalized = normalizePort(value);
|
|
121
|
+
if (normalized) {
|
|
122
|
+
saveDaemonPort(normalized).catch(() => {});
|
|
123
|
+
}
|
|
124
|
+
setMessage(null);
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
const handleQrTarget = useCallback((target: ParsedComputerTarget) => {
|
|
128
|
+
setComputerHost(target.computerHost);
|
|
129
|
+
setMetroPort(target.metroPort);
|
|
130
|
+
saveComputerTarget(`${target.computerHost}:${target.metroPort}`).catch(() => {});
|
|
70
131
|
setMessage('Computer IP updated from QR code.');
|
|
71
132
|
}, []);
|
|
72
133
|
|
|
@@ -75,22 +136,28 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
75
136
|
setMessage('Enter your computer IP first.');
|
|
76
137
|
return false;
|
|
77
138
|
}
|
|
139
|
+
if (!normalizePort(daemonPort)) {
|
|
140
|
+
setMessage('Enter a valid desktop logs port.');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
78
143
|
return true;
|
|
79
|
-
}, [computerHost, isSim]);
|
|
144
|
+
}, [computerHost, daemonPort, isSim]);
|
|
80
145
|
|
|
81
146
|
const configureDaemon = useCallback(() => {
|
|
82
147
|
const normalizedHost = isSim ? '' : (normalizeComputerHost(computerHost) ?? '');
|
|
148
|
+
const normalizedDaemonPort = normalizePort(daemonPort) ?? DEFAULT_DAEMON_PORT;
|
|
149
|
+
const deviceHost = isSim ? '' : buildDaemonDeviceHost(normalizedHost, normalizedDaemonPort);
|
|
83
150
|
const settings: DaemonSettings = {
|
|
84
151
|
mode: isSim ? 'simulator' : 'device',
|
|
85
152
|
endpoint: '',
|
|
86
|
-
deviceHost
|
|
153
|
+
deviceHost,
|
|
87
154
|
token: '',
|
|
88
155
|
};
|
|
89
156
|
daemonClient.configure(settings);
|
|
90
157
|
const normalized = normalizeDaemonSettings(settings);
|
|
91
|
-
const endpoint = normalized.endpoint || (isSim ? getDefaultDaemonEndpoint() : buildDeviceDaemonEndpoint(
|
|
158
|
+
const endpoint = normalized.endpoint || (isSim ? getDefaultDaemonEndpoint() : buildDeviceDaemonEndpoint(deviceHost));
|
|
92
159
|
return { ...normalized, endpoint };
|
|
93
|
-
}, [computerHost, isSim]);
|
|
160
|
+
}, [computerHost, daemonPort, isSim]);
|
|
94
161
|
|
|
95
162
|
const toggleLiveSync = useCallback(async () => {
|
|
96
163
|
if (streaming) {
|
|
@@ -102,7 +169,9 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
102
169
|
return;
|
|
103
170
|
}
|
|
104
171
|
|
|
105
|
-
if (!validateSettings())
|
|
172
|
+
if (!validateSettings()) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
106
175
|
|
|
107
176
|
const daemonOptions = configureDaemon();
|
|
108
177
|
setMessage('Checking desktop connection...');
|
|
@@ -144,7 +213,9 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
144
213
|
}, [configureDaemon, streaming, validateSettings]);
|
|
145
214
|
|
|
146
215
|
const sendOnce = useCallback(async () => {
|
|
147
|
-
if (!validateSettings())
|
|
216
|
+
if (!validateSettings()) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
148
219
|
|
|
149
220
|
const daemonOptions = configureDaemon();
|
|
150
221
|
setSending(true);
|
|
@@ -177,18 +248,51 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
177
248
|
}
|
|
178
249
|
}, [configureDaemon, validateSettings]);
|
|
179
250
|
|
|
180
|
-
const
|
|
251
|
+
const applyRemoteBundle = useCallback(async () => {
|
|
252
|
+
if (!metroTarget) {
|
|
253
|
+
setMessage('Enter a valid computer IP and Metro port.');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!snapshot.nativeMetroAvailable) {
|
|
257
|
+
setMessage(describeMetroFailure({ reason: 'native_unavailable' }));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
181
260
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
261
|
+
setMetroBusy(true);
|
|
262
|
+
setMessage('Checking Metro...');
|
|
263
|
+
try {
|
|
264
|
+
const result = await applyMetroBundle(metroTarget.host, metroTarget.port);
|
|
265
|
+
if (result.ok) {
|
|
266
|
+
setMessage(`Using Metro at ${result.hostPort}. Reloading...`);
|
|
267
|
+
} else {
|
|
268
|
+
setMessage(describeMetroFailure(result));
|
|
269
|
+
}
|
|
270
|
+
} finally {
|
|
271
|
+
setMetroBusy(false);
|
|
272
|
+
}
|
|
273
|
+
}, [metroTarget, snapshot.nativeMetroAvailable]);
|
|
188
274
|
|
|
189
|
-
|
|
275
|
+
const resetRemoteBundle = useCallback(async () => {
|
|
276
|
+
if (!snapshot.nativeMetroAvailable) {
|
|
277
|
+
setMessage(describeMetroFailure({ reason: 'native_unavailable' }));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
190
280
|
|
|
191
|
-
|
|
281
|
+
setMetroBusy(true);
|
|
282
|
+
try {
|
|
283
|
+
const result = await resetMetroBundle();
|
|
284
|
+
if (result.ok) {
|
|
285
|
+
setMessage('Metro host reset. Reloading...');
|
|
286
|
+
} else {
|
|
287
|
+
setMessage(describeMetroFailure(result));
|
|
288
|
+
}
|
|
289
|
+
} finally {
|
|
290
|
+
setMetroBusy(false);
|
|
291
|
+
}
|
|
292
|
+
}, [snapshot.nativeMetroAvailable]);
|
|
293
|
+
|
|
294
|
+
const canConnect = isSim || (Boolean(normalizeComputerHost(computerHost)) && Boolean(normalizePort(daemonPort)));
|
|
295
|
+
const canUseMetro = Boolean(metroTarget) && snapshot.nativeMetroAvailable && !metroBusy;
|
|
192
296
|
const busy = sending || syncState === 'checking';
|
|
193
297
|
|
|
194
298
|
return (
|
|
@@ -197,7 +301,7 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
197
301
|
|
|
198
302
|
{isSim ? (
|
|
199
303
|
<View style={styles.badge}>
|
|
200
|
-
<Text style={styles.badgeText}>Simulator
|
|
304
|
+
<Text style={styles.badgeText}>Simulator/emulator - using {getSimulatorMetroHost()}</Text>
|
|
201
305
|
</View>
|
|
202
306
|
) : (
|
|
203
307
|
<View style={styles.section}>
|
|
@@ -226,6 +330,40 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
226
330
|
</View>
|
|
227
331
|
)}
|
|
228
332
|
|
|
333
|
+
<View style={styles.section}>
|
|
334
|
+
<Text style={styles.label}>Ports</Text>
|
|
335
|
+
<View style={styles.portRow}>
|
|
336
|
+
<View style={styles.portField}>
|
|
337
|
+
<Text style={styles.portLabel}>Metro</Text>
|
|
338
|
+
<TextInput
|
|
339
|
+
style={styles.portInput}
|
|
340
|
+
value={metroPort}
|
|
341
|
+
onChangeText={handleMetroPortChange}
|
|
342
|
+
placeholder={DEFAULT_METRO_PORT}
|
|
343
|
+
placeholderTextColor={Colors.textLight}
|
|
344
|
+
autoCapitalize="none"
|
|
345
|
+
autoCorrect={false}
|
|
346
|
+
keyboardType="number-pad"
|
|
347
|
+
returnKeyType="done"
|
|
348
|
+
/>
|
|
349
|
+
</View>
|
|
350
|
+
<View style={styles.portField}>
|
|
351
|
+
<Text style={styles.portLabel}>Logs</Text>
|
|
352
|
+
<TextInput
|
|
353
|
+
style={styles.portInput}
|
|
354
|
+
value={daemonPort}
|
|
355
|
+
onChangeText={handleDaemonPortChange}
|
|
356
|
+
placeholder={DEFAULT_DAEMON_PORT}
|
|
357
|
+
placeholderTextColor={Colors.textLight}
|
|
358
|
+
autoCapitalize="none"
|
|
359
|
+
autoCorrect={false}
|
|
360
|
+
keyboardType="number-pad"
|
|
361
|
+
returnKeyType="done"
|
|
362
|
+
/>
|
|
363
|
+
</View>
|
|
364
|
+
</View>
|
|
365
|
+
</View>
|
|
366
|
+
|
|
229
367
|
<View style={styles.actions}>
|
|
230
368
|
<TouchableOpacity
|
|
231
369
|
style={[styles.primaryButton, (!canConnect || busy) && styles.buttonDisabled]}
|
|
@@ -254,65 +392,56 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
254
392
|
<View style={styles.section}>
|
|
255
393
|
<Text style={styles.sectionTitle}>Remote JS Bundle</Text>
|
|
256
394
|
<Text style={styles.sectionDesc}>
|
|
257
|
-
|
|
395
|
+
Apply this Metro host to React Native dev settings and reload the app.
|
|
258
396
|
</Text>
|
|
259
397
|
|
|
260
398
|
{!metroUrls ? (
|
|
261
399
|
<View style={styles.stepCard}>
|
|
262
|
-
<Text style={styles.stepHint}>Enter your computer IP
|
|
400
|
+
<Text style={styles.stepHint}>Enter your computer IP and Metro port to get started.</Text>
|
|
263
401
|
</View>
|
|
264
402
|
) : (
|
|
265
|
-
|
|
266
|
-
<View style={styles.
|
|
267
|
-
<
|
|
268
|
-
|
|
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>
|
|
285
|
-
</View>
|
|
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>
|
|
403
|
+
<View style={styles.stepCard}>
|
|
404
|
+
<View style={styles.urlRow}>
|
|
405
|
+
<Text style={styles.urlLabel}>HTTP</Text>
|
|
406
|
+
<Text style={styles.urlText} numberOfLines={1}>{metroUrls.httpUrl}</Text>
|
|
297
407
|
</View>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
<
|
|
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>
|
|
408
|
+
<View style={styles.urlRow}>
|
|
409
|
+
<Text style={styles.urlLabel}>Expo</Text>
|
|
410
|
+
<Text style={styles.urlText} numberOfLines={1}>{metroUrls.expUrl}</Text>
|
|
307
411
|
</View>
|
|
308
|
-
|
|
412
|
+
</View>
|
|
309
413
|
)}
|
|
414
|
+
|
|
415
|
+
<View style={styles.actions}>
|
|
416
|
+
<TouchableOpacity
|
|
417
|
+
style={[styles.primaryButton, !canUseMetro && styles.buttonDisabled]}
|
|
418
|
+
onPress={applyRemoteBundle}
|
|
419
|
+
disabled={!canUseMetro}
|
|
420
|
+
activeOpacity={0.75}
|
|
421
|
+
>
|
|
422
|
+
<Text style={styles.primaryButtonText}>
|
|
423
|
+
{metroBusy ? 'Checking...' : 'Use Metro Bundle'}
|
|
424
|
+
</Text>
|
|
425
|
+
</TouchableOpacity>
|
|
426
|
+
<TouchableOpacity
|
|
427
|
+
style={[styles.secondaryButton, (!snapshot.nativeMetroAvailable || metroBusy) && styles.buttonDisabled]}
|
|
428
|
+
onPress={resetRemoteBundle}
|
|
429
|
+
disabled={!snapshot.nativeMetroAvailable || metroBusy}
|
|
430
|
+
activeOpacity={0.75}
|
|
431
|
+
>
|
|
432
|
+
<Text style={styles.secondaryButtonText}>Reset</Text>
|
|
433
|
+
</TouchableOpacity>
|
|
434
|
+
</View>
|
|
435
|
+
|
|
436
|
+
{!snapshot.nativeMetroAvailable ? (
|
|
437
|
+
<Text style={styles.hint}>Native DevConnect requires pod install / Gradle sync and app rebuild.</Text>
|
|
438
|
+
) : null}
|
|
310
439
|
</View>
|
|
311
440
|
</ScrollView>
|
|
312
441
|
<DevConnectQrScanner
|
|
313
442
|
visible={qrVisible}
|
|
314
443
|
onClose={() => setQrVisible(false)}
|
|
315
|
-
|
|
444
|
+
onScanTarget={handleQrTarget}
|
|
316
445
|
/>
|
|
317
446
|
</KeyboardAvoidingView>
|
|
318
447
|
);
|
|
@@ -360,6 +489,20 @@ const styles = StyleSheet.create({
|
|
|
360
489
|
borderColor: Colors.primary,
|
|
361
490
|
},
|
|
362
491
|
scanButtonText: { color: Colors.primary, fontSize: 13, fontWeight: '600' },
|
|
492
|
+
portRow: { flexDirection: 'row', gap: 10 },
|
|
493
|
+
portField: { flex: 1 },
|
|
494
|
+
portLabel: { fontSize: 11, color: Colors.textSecondary, marginBottom: 4 },
|
|
495
|
+
portInput: {
|
|
496
|
+
backgroundColor: Colors.surface,
|
|
497
|
+
borderWidth: 1,
|
|
498
|
+
borderColor: Colors.border,
|
|
499
|
+
borderRadius: 8,
|
|
500
|
+
paddingHorizontal: 12,
|
|
501
|
+
paddingVertical: 9,
|
|
502
|
+
fontSize: 13,
|
|
503
|
+
color: Colors.text,
|
|
504
|
+
fontFamily: 'Courier',
|
|
505
|
+
},
|
|
363
506
|
actions: { flexDirection: 'row', gap: 10, marginTop: 4, marginBottom: 12 },
|
|
364
507
|
primaryButton: {
|
|
365
508
|
flex: 1,
|
|
@@ -383,7 +526,7 @@ const styles = StyleSheet.create({
|
|
|
383
526
|
secondaryButtonText: { color: Colors.primary, fontSize: 14, fontWeight: '600' },
|
|
384
527
|
buttonDisabled: { opacity: 0.5 },
|
|
385
528
|
message: { fontSize: 12, lineHeight: 17, color: Colors.textSecondary, marginBottom: 12 },
|
|
386
|
-
hint: { fontSize: 12, color: Colors.textLight },
|
|
529
|
+
hint: { fontSize: 12, color: Colors.textLight, lineHeight: 17 },
|
|
387
530
|
stepCard: {
|
|
388
531
|
backgroundColor: Colors.surface,
|
|
389
532
|
borderWidth: 1,
|
|
@@ -393,23 +536,8 @@ const styles = StyleSheet.create({
|
|
|
393
536
|
marginBottom: 8,
|
|
394
537
|
},
|
|
395
538
|
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
539
|
urlLabel: {
|
|
540
|
+
minWidth: 40,
|
|
413
541
|
fontSize: 10,
|
|
414
542
|
fontWeight: '600',
|
|
415
543
|
color: Colors.primary,
|
|
@@ -417,21 +545,15 @@ const styles = StyleSheet.create({
|
|
|
417
545
|
paddingHorizontal: 6,
|
|
418
546
|
paddingVertical: 2,
|
|
419
547
|
borderRadius: 4,
|
|
420
|
-
marginRight:
|
|
548
|
+
marginRight: 8,
|
|
549
|
+
textAlign: 'center',
|
|
421
550
|
},
|
|
422
551
|
urlRow: {
|
|
423
552
|
flexDirection: 'row',
|
|
424
553
|
alignItems: 'center',
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
borderRadius: 8,
|
|
429
|
-
paddingLeft: 12,
|
|
430
|
-
paddingRight: 4,
|
|
431
|
-
paddingVertical: 4,
|
|
432
|
-
marginBottom: 8,
|
|
554
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
555
|
+
borderBottomColor: Colors.border,
|
|
556
|
+
paddingVertical: 7,
|
|
433
557
|
},
|
|
434
|
-
urlText: { flex: 1, fontSize: 13, fontFamily: 'Courier', color: Colors.text
|
|
435
|
-
copyButton: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 6, backgroundColor: Colors.primary },
|
|
436
|
-
copyButtonText: { color: '#fff', fontSize: 12, fontWeight: '600' },
|
|
558
|
+
urlText: { flex: 1, fontSize: 13, fontFamily: 'Courier', color: Colors.text },
|
|
437
559
|
});
|
|
@@ -1,23 +1,66 @@
|
|
|
1
1
|
import { daemonClient } from '../../utils/DaemonClient';
|
|
2
2
|
import { getPreference, KEYS, setPreference } from '../../utils/debugPreferences';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_DAEMON_PORT,
|
|
5
|
+
DEFAULT_METRO_PORT,
|
|
6
|
+
buildDaemonDeviceHost,
|
|
7
|
+
normalizeComputerHost,
|
|
8
|
+
normalizePort,
|
|
9
|
+
parseComputerTarget,
|
|
10
|
+
type ParsedComputerTarget,
|
|
11
|
+
} from './devConnectUtils';
|
|
4
12
|
import { isSimulator } from './platformDetect';
|
|
5
13
|
|
|
6
14
|
export interface DevConnectPreferences {
|
|
7
15
|
computerHost: string;
|
|
16
|
+
metroPort: string;
|
|
17
|
+
daemonPort: string;
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
export async function loadDevConnectPreferences(): Promise<DevConnectPreferences> {
|
|
11
21
|
const storedHost = await getPreference(KEYS.computerHost);
|
|
22
|
+
const storedMetroPort = await getPreference(KEYS.metroPort);
|
|
23
|
+
const storedDaemonPort = await getPreference(KEYS.daemonPort);
|
|
12
24
|
return {
|
|
13
25
|
computerHost: storedHost ? normalizeComputerHost(storedHost) ?? '' : '',
|
|
26
|
+
metroPort: storedMetroPort ? normalizePort(storedMetroPort) ?? DEFAULT_METRO_PORT : DEFAULT_METRO_PORT,
|
|
27
|
+
daemonPort: storedDaemonPort ? normalizePort(storedDaemonPort) ?? DEFAULT_DAEMON_PORT : DEFAULT_DAEMON_PORT,
|
|
14
28
|
};
|
|
15
29
|
}
|
|
16
30
|
|
|
31
|
+
export async function saveComputerTarget(value: string): Promise<ParsedComputerTarget | null> {
|
|
32
|
+
const target = parseComputerTarget(value);
|
|
33
|
+
if (!target) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await setPreference(KEYS.computerHost, target.computerHost);
|
|
38
|
+
await setPreference(KEYS.metroPort, target.metroPort);
|
|
39
|
+
return target;
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
export async function saveComputerHost(value: string): Promise<string | null> {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
43
|
+
const target = await saveComputerTarget(value);
|
|
44
|
+
return target?.computerHost ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function saveMetroPort(value: string): Promise<string | null> {
|
|
48
|
+
const normalized = normalizePort(value);
|
|
49
|
+
if (!normalized) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await setPreference(KEYS.metroPort, normalized);
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function saveDaemonPort(value: string): Promise<string | null> {
|
|
58
|
+
const normalized = normalizePort(value);
|
|
59
|
+
if (!normalized) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await setPreference(KEYS.daemonPort, normalized);
|
|
21
64
|
return normalized;
|
|
22
65
|
}
|
|
23
66
|
|
|
@@ -27,7 +70,9 @@ export async function restoreDevConnectSettingsToDaemon(): Promise<void> {
|
|
|
27
70
|
daemonClient.configure({
|
|
28
71
|
mode,
|
|
29
72
|
endpoint: '',
|
|
30
|
-
deviceHost: mode === 'simulator'
|
|
73
|
+
deviceHost: mode === 'simulator'
|
|
74
|
+
? ''
|
|
75
|
+
: buildDaemonDeviceHost(preferences.computerHost, preferences.daemonPort),
|
|
31
76
|
token: '',
|
|
32
77
|
});
|
|
33
78
|
}
|