react-native-debug-toolkit 3.2.3 → 3.2.5
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/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitDevConnectModule.java +80 -10
- package/ios/DebugToolkitDevConnect.mm +84 -19
- package/lib/commonjs/core/initialize.js +15 -3
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/features/devConnect/DevConnectTab.js +121 -58
- package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectPreferences.js +6 -2
- package/lib/commonjs/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/commonjs/features/devConnect/devConnectUtils.js +8 -0
- package/lib/commonjs/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/commonjs/features/devConnect/index.js +33 -5
- package/lib/commonjs/features/devConnect/index.js.map +1 -1
- package/lib/commonjs/features/devConnect/nativeDevConnect.js +26 -0
- package/lib/commonjs/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/commonjs/ui/DebugView.js +10 -2
- package/lib/commonjs/ui/DebugView.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +5 -0
- package/lib/commonjs/utils/DaemonClient.js.map +1 -1
- package/lib/module/core/initialize.js +16 -4
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +122 -59
- package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/module/features/devConnect/devConnectPreferences.js +6 -2
- package/lib/module/features/devConnect/devConnectPreferences.js.map +1 -1
- package/lib/module/features/devConnect/devConnectUtils.js +7 -0
- package/lib/module/features/devConnect/devConnectUtils.js.map +1 -1
- package/lib/module/features/devConnect/index.js +30 -7
- package/lib/module/features/devConnect/index.js.map +1 -1
- package/lib/module/features/devConnect/nativeDevConnect.js +24 -0
- package/lib/module/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/module/ui/DebugView.js +11 -3
- package/lib/module/ui/DebugView.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +5 -0
- package/lib/module/utils/DaemonClient.js.map +1 -1
- package/lib/typescript/src/core/initialize.d.ts +4 -2
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts +1 -0
- package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/index.d.ts +1 -0
- package/lib/typescript/src/features/devConnect/index.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts +2 -0
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/types.d.ts +5 -1
- package/lib/typescript/src/features/devConnect/types.d.ts.map +1 -1
- package/lib/typescript/src/ui/DebugView.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +2 -0
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
- package/package.json +2 -10
- package/src/core/initialize.ts +17 -5
- package/src/features/devConnect/DevConnectTab.tsx +120 -45
- package/src/features/devConnect/devConnectPreferences.ts +7 -2
- package/src/features/devConnect/devConnectUtils.ts +8 -0
- package/src/features/devConnect/index.ts +31 -7
- package/src/features/devConnect/nativeDevConnect.ts +28 -0
- package/src/features/devConnect/types.ts +9 -1
- package/src/ui/DebugView.tsx +12 -3
- package/src/utils/DaemonClient.ts +7 -0
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js +0 -248
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js.map +0 -1
- package/lib/commonjs/features/devConnect/cameraKit.js +0 -54
- package/lib/commonjs/features/devConnect/cameraKit.js.map +0 -1
- package/lib/module/features/devConnect/DevConnectQrScanner.js +0 -243
- package/lib/module/features/devConnect/DevConnectQrScanner.js.map +0 -1
- package/lib/module/features/devConnect/cameraKit.js +0 -49
- package/lib/module/features/devConnect/cameraKit.js.map +0 -1
- package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts +0 -10
- package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts.map +0 -1
- package/lib/typescript/src/features/devConnect/cameraKit.d.ts +0 -47
- package/lib/typescript/src/features/devConnect/cameraKit.d.ts.map +0 -1
- package/src/features/devConnect/DevConnectQrScanner.tsx +0 -214
- package/src/features/devConnect/cameraKit.ts +0 -93
|
@@ -28,16 +28,15 @@ import {
|
|
|
28
28
|
normalizeComputerHost,
|
|
29
29
|
normalizePort,
|
|
30
30
|
parseComputerTarget,
|
|
31
|
-
type ParsedComputerTarget,
|
|
32
31
|
} from './devConnectUtils';
|
|
33
32
|
import {
|
|
33
|
+
saveComputerHost,
|
|
34
34
|
saveComputerTarget,
|
|
35
35
|
saveDaemonPort,
|
|
36
36
|
saveMetroPort,
|
|
37
37
|
} from './devConnectPreferences';
|
|
38
38
|
import { applyMetroBundle, resetMetroBundle } from './nativeDevConnect';
|
|
39
|
-
import type { DevConnectState } from './types';
|
|
40
|
-
import { DevConnectQrScanner } from './DevConnectQrScanner';
|
|
39
|
+
import type { DevConnectFeatureControls, DevConnectSettingsPatch, DevConnectState } from './types';
|
|
41
40
|
|
|
42
41
|
const CONNECTION_TIMEOUT_MS = 2000;
|
|
43
42
|
|
|
@@ -63,7 +62,7 @@ function describeMetroFailure(result: { reason: string; error?: string }): strin
|
|
|
63
62
|
return result.error ? `Metro switch failed: ${result.error}` : 'Metro switch failed.';
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectState>) {
|
|
65
|
+
export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<DevConnectState>) {
|
|
67
66
|
const inputRef = useRef<TextInput>(null);
|
|
68
67
|
const [computerHost, setComputerHost] = useState(snapshot.computerHost);
|
|
69
68
|
const [metroPort, setMetroPort] = useState(snapshot.metroPort);
|
|
@@ -73,17 +72,29 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
73
72
|
const [message, setMessage] = useState<string | null>(null);
|
|
74
73
|
const [sending, setSending] = useState(false);
|
|
75
74
|
const [metroBusy, setMetroBusy] = useState(false);
|
|
76
|
-
const [qrVisible, setQrVisible] = useState(false);
|
|
77
75
|
|
|
78
76
|
const isSim = snapshot.isSimulator;
|
|
79
77
|
|
|
78
|
+
const updateFeatureSettings = useCallback((patch: DevConnectSettingsPatch) => {
|
|
79
|
+
(feature as unknown as DevConnectFeatureControls).updateSettings?.(patch);
|
|
80
|
+
}, [feature]);
|
|
81
|
+
|
|
80
82
|
useEffect(() => {
|
|
81
83
|
setComputerHost(snapshot.computerHost);
|
|
84
|
+
}, [snapshot.computerHost]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
82
87
|
setMetroPort(snapshot.metroPort);
|
|
88
|
+
}, [snapshot.metroPort]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
83
91
|
setDaemonPort(snapshot.daemonPort);
|
|
92
|
+
}, [snapshot.daemonPort]);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
84
95
|
setStreaming(snapshot.streaming);
|
|
85
96
|
setSyncState(snapshot.streaming ? 'running' : 'idle');
|
|
86
|
-
}, [snapshot.
|
|
97
|
+
}, [snapshot.streaming]);
|
|
87
98
|
|
|
88
99
|
const metroHost = isSim ? getSimulatorMetroHost() : computerHost;
|
|
89
100
|
const metroTarget = useMemo(
|
|
@@ -100,36 +111,97 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
100
111
|
const target = parseComputerTarget(value);
|
|
101
112
|
if (target) {
|
|
102
113
|
setMetroPort(target.metroPort);
|
|
103
|
-
saveComputerTarget(value)
|
|
114
|
+
saveComputerTarget(value)
|
|
115
|
+
.then((savedTarget) => {
|
|
116
|
+
if (savedTarget) {
|
|
117
|
+
updateFeatureSettings({
|
|
118
|
+
computerHost: savedTarget.computerHost,
|
|
119
|
+
metroPort: savedTarget.metroPort,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.catch(() => {});
|
|
104
124
|
}
|
|
105
125
|
setSyncState((prev) => (prev === 'failed' ? 'idle' : prev));
|
|
106
126
|
setMessage(null);
|
|
107
|
-
}, []);
|
|
127
|
+
}, [updateFeatureSettings]);
|
|
108
128
|
|
|
109
129
|
const handleMetroPortChange = useCallback((value: string) => {
|
|
110
130
|
setMetroPort(value);
|
|
111
131
|
const normalized = normalizePort(value);
|
|
112
132
|
if (normalized) {
|
|
113
|
-
saveMetroPort(normalized)
|
|
133
|
+
saveMetroPort(normalized)
|
|
134
|
+
.then(() => updateFeatureSettings({ metroPort: normalized }))
|
|
135
|
+
.catch(() => {});
|
|
114
136
|
}
|
|
115
137
|
setMessage(null);
|
|
116
|
-
}, []);
|
|
138
|
+
}, [updateFeatureSettings]);
|
|
117
139
|
|
|
118
140
|
const handleDaemonPortChange = useCallback((value: string) => {
|
|
119
141
|
setDaemonPort(value);
|
|
120
142
|
const normalized = normalizePort(value);
|
|
121
143
|
if (normalized) {
|
|
122
|
-
saveDaemonPort(normalized)
|
|
144
|
+
saveDaemonPort(normalized)
|
|
145
|
+
.then(() => updateFeatureSettings({ daemonPort: normalized }))
|
|
146
|
+
.catch(() => {});
|
|
123
147
|
}
|
|
124
148
|
setMessage(null);
|
|
125
|
-
}, []);
|
|
149
|
+
}, [updateFeatureSettings]);
|
|
150
|
+
|
|
151
|
+
const persistConnectionSettings = useCallback(async (): Promise<boolean> => {
|
|
152
|
+
const normalizedDaemonPort = normalizePort(daemonPort);
|
|
153
|
+
if (!normalizedDaemonPort) {
|
|
154
|
+
setMessage('Enter a valid desktop logs port.');
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const patch: DevConnectSettingsPatch = { daemonPort: normalizedDaemonPort };
|
|
159
|
+
const writes: Array<Promise<unknown>> = [saveDaemonPort(normalizedDaemonPort)];
|
|
160
|
+
setDaemonPort(normalizedDaemonPort);
|
|
161
|
+
|
|
162
|
+
if (!isSim) {
|
|
163
|
+
const normalizedHost = normalizeComputerHost(computerHost);
|
|
164
|
+
if (!normalizedHost) {
|
|
165
|
+
setMessage('Enter your computer IP first.');
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
patch.computerHost = normalizedHost;
|
|
169
|
+
writes.push(saveComputerHost(normalizedHost));
|
|
170
|
+
setComputerHost(normalizedHost);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const normalizedMetroPort = normalizePort(metroPort);
|
|
174
|
+
if (normalizedMetroPort) {
|
|
175
|
+
patch.metroPort = normalizedMetroPort;
|
|
176
|
+
writes.push(saveMetroPort(normalizedMetroPort));
|
|
177
|
+
setMetroPort(normalizedMetroPort);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await Promise.all(writes);
|
|
181
|
+
updateFeatureSettings(patch);
|
|
182
|
+
return true;
|
|
183
|
+
}, [computerHost, daemonPort, isSim, metroPort, updateFeatureSettings]);
|
|
184
|
+
|
|
185
|
+
const persistMetroSettings = useCallback(async (): Promise<boolean> => {
|
|
186
|
+
if (!metroTarget) {
|
|
187
|
+
setMessage('Enter a valid computer IP and Metro port.');
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
126
190
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
setMetroPort(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
const patch: DevConnectSettingsPatch = { metroPort: metroTarget.port };
|
|
192
|
+
const writes: Array<Promise<unknown>> = [saveMetroPort(metroTarget.port)];
|
|
193
|
+
setMetroPort(metroTarget.port);
|
|
194
|
+
|
|
195
|
+
if (!isSim) {
|
|
196
|
+
patch.computerHost = metroTarget.host;
|
|
197
|
+
writes.push(saveComputerTarget(metroTarget.hostPort));
|
|
198
|
+
setComputerHost(metroTarget.host);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await Promise.all(writes);
|
|
202
|
+
updateFeatureSettings(patch);
|
|
203
|
+
return true;
|
|
204
|
+
}, [isSim, metroTarget, updateFeatureSettings]);
|
|
133
205
|
|
|
134
206
|
const validateSettings = useCallback((): boolean => {
|
|
135
207
|
if (!isSim && !normalizeComputerHost(computerHost)) {
|
|
@@ -172,6 +244,9 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
172
244
|
if (!validateSettings()) {
|
|
173
245
|
return;
|
|
174
246
|
}
|
|
247
|
+
if (!(await persistConnectionSettings())) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
175
250
|
|
|
176
251
|
const daemonOptions = configureDaemon();
|
|
177
252
|
setMessage('Checking desktop connection...');
|
|
@@ -210,12 +285,15 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
210
285
|
},
|
|
211
286
|
});
|
|
212
287
|
setStreaming(true);
|
|
213
|
-
}, [configureDaemon, streaming, validateSettings]);
|
|
288
|
+
}, [configureDaemon, persistConnectionSettings, streaming, validateSettings]);
|
|
214
289
|
|
|
215
290
|
const sendOnce = useCallback(async () => {
|
|
216
291
|
if (!validateSettings()) {
|
|
217
292
|
return;
|
|
218
293
|
}
|
|
294
|
+
if (!(await persistConnectionSettings())) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
219
297
|
|
|
220
298
|
const daemonOptions = configureDaemon();
|
|
221
299
|
setSending(true);
|
|
@@ -246,7 +324,7 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
246
324
|
} finally {
|
|
247
325
|
setSending(false);
|
|
248
326
|
}
|
|
249
|
-
}, [configureDaemon, validateSettings]);
|
|
327
|
+
}, [configureDaemon, persistConnectionSettings, validateSettings]);
|
|
250
328
|
|
|
251
329
|
const applyRemoteBundle = useCallback(async () => {
|
|
252
330
|
if (!metroTarget) {
|
|
@@ -261,6 +339,9 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
261
339
|
setMetroBusy(true);
|
|
262
340
|
setMessage('Checking Metro...');
|
|
263
341
|
try {
|
|
342
|
+
if (!(await persistMetroSettings())) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
264
345
|
const result = await applyMetroBundle(metroTarget.host, metroTarget.port);
|
|
265
346
|
if (result.ok) {
|
|
266
347
|
setMessage(`Using Metro at ${result.hostPort}. Reloading...`);
|
|
@@ -270,7 +351,7 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
270
351
|
} finally {
|
|
271
352
|
setMetroBusy(false);
|
|
272
353
|
}
|
|
273
|
-
}, [metroTarget, snapshot.nativeMetroAvailable]);
|
|
354
|
+
}, [metroTarget, persistMetroSettings, snapshot.nativeMetroAvailable]);
|
|
274
355
|
|
|
275
356
|
const resetRemoteBundle = useCallback(async () => {
|
|
276
357
|
if (!snapshot.nativeMetroAvailable) {
|
|
@@ -294,6 +375,8 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
294
375
|
const canConnect = isSim || (Boolean(normalizeComputerHost(computerHost)) && Boolean(normalizePort(daemonPort)));
|
|
295
376
|
const canUseMetro = Boolean(metroTarget) && snapshot.nativeMetroAvailable && !metroBusy;
|
|
296
377
|
const busy = sending || syncState === 'checking';
|
|
378
|
+
const subnetPrefix = snapshot.subnetPrefix;
|
|
379
|
+
const ipPlaceholder = subnetPrefix ? `${subnetPrefix}...` : '192.168.1.10';
|
|
297
380
|
|
|
298
381
|
return (
|
|
299
382
|
<KeyboardAvoidingView style={styles.container} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
@@ -312,7 +395,7 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
312
395
|
style={styles.input}
|
|
313
396
|
value={computerHost}
|
|
314
397
|
onChangeText={handleHostChange}
|
|
315
|
-
placeholder=
|
|
398
|
+
placeholder={ipPlaceholder}
|
|
316
399
|
placeholderTextColor={Colors.textLight}
|
|
317
400
|
autoCapitalize="none"
|
|
318
401
|
autoCorrect={false}
|
|
@@ -321,12 +404,19 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
321
404
|
onSubmitEditing={() => inputRef.current?.blur()}
|
|
322
405
|
editable={!streaming}
|
|
323
406
|
/>
|
|
324
|
-
{snapshot.qrAvailable ? (
|
|
325
|
-
<TouchableOpacity style={styles.scanButton} onPress={() => setQrVisible(true)} disabled={streaming} activeOpacity={0.7}>
|
|
326
|
-
<Text style={styles.scanButtonText}>Scan</Text>
|
|
327
|
-
</TouchableOpacity>
|
|
328
|
-
) : null}
|
|
329
407
|
</View>
|
|
408
|
+
{subnetPrefix && !computerHost ? (
|
|
409
|
+
<TouchableOpacity
|
|
410
|
+
style={styles.subnetHint}
|
|
411
|
+
onPress={() => {
|
|
412
|
+
setComputerHost(subnetPrefix);
|
|
413
|
+
inputRef.current?.focus();
|
|
414
|
+
}}
|
|
415
|
+
activeOpacity={0.6}
|
|
416
|
+
>
|
|
417
|
+
<Text style={styles.subnetHintText}>Tap to fill: {subnetPrefix}</Text>
|
|
418
|
+
</TouchableOpacity>
|
|
419
|
+
) : null}
|
|
330
420
|
</View>
|
|
331
421
|
)}
|
|
332
422
|
|
|
@@ -438,11 +528,6 @@ export function DevConnectTab({ snapshot }: DebugFeatureRenderProps<DevConnectSt
|
|
|
438
528
|
) : null}
|
|
439
529
|
</View>
|
|
440
530
|
</ScrollView>
|
|
441
|
-
<DevConnectQrScanner
|
|
442
|
-
visible={qrVisible}
|
|
443
|
-
onClose={() => setQrVisible(false)}
|
|
444
|
-
onScanTarget={handleQrTarget}
|
|
445
|
-
/>
|
|
446
531
|
</KeyboardAvoidingView>
|
|
447
532
|
);
|
|
448
533
|
}
|
|
@@ -464,7 +549,9 @@ const styles = StyleSheet.create({
|
|
|
464
549
|
sectionTitle: { fontSize: 14, fontWeight: '600', color: Colors.text, marginBottom: 4 },
|
|
465
550
|
sectionDesc: { fontSize: 12, color: Colors.textSecondary, marginBottom: 10, lineHeight: 17 },
|
|
466
551
|
label: { fontSize: 13, fontWeight: '500', color: Colors.textSecondary, marginBottom: 6 },
|
|
467
|
-
inputRow: { flexDirection: 'row', alignItems: 'center'
|
|
552
|
+
inputRow: { flexDirection: 'row', alignItems: 'center' },
|
|
553
|
+
subnetHint: { marginTop: 6 },
|
|
554
|
+
subnetHintText: { fontSize: 12, color: Colors.primary, fontWeight: '500' },
|
|
468
555
|
input: {
|
|
469
556
|
flex: 1,
|
|
470
557
|
backgroundColor: Colors.surface,
|
|
@@ -477,18 +564,6 @@ const styles = StyleSheet.create({
|
|
|
477
564
|
color: Colors.text,
|
|
478
565
|
fontFamily: 'Courier',
|
|
479
566
|
},
|
|
480
|
-
scanButton: {
|
|
481
|
-
minWidth: 62,
|
|
482
|
-
alignItems: 'center',
|
|
483
|
-
justifyContent: 'center',
|
|
484
|
-
paddingHorizontal: 12,
|
|
485
|
-
paddingVertical: 10,
|
|
486
|
-
borderRadius: 8,
|
|
487
|
-
backgroundColor: Colors.surface,
|
|
488
|
-
borderWidth: 1,
|
|
489
|
-
borderColor: Colors.primary,
|
|
490
|
-
},
|
|
491
|
-
scanButtonText: { color: Colors.primary, fontSize: 13, fontWeight: '600' },
|
|
492
567
|
portRow: { flexDirection: 'row', gap: 10 },
|
|
493
568
|
portField: { flex: 1 },
|
|
494
569
|
portLabel: { fontSize: 11, color: Colors.textSecondary, marginBottom: 4 },
|
|
@@ -40,8 +40,13 @@ export async function saveComputerTarget(value: string): Promise<ParsedComputerT
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export async function saveComputerHost(value: string): Promise<string | null> {
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const host = normalizeComputerHost(value);
|
|
44
|
+
if (!host) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await setPreference(KEYS.computerHost, host);
|
|
49
|
+
return host;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
export async function saveMetroPort(value: string): Promise<string | null> {
|
|
@@ -164,3 +164,11 @@ export function buildDaemonDeviceHost(computerHost: string, daemonPort: string):
|
|
|
164
164
|
const port = normalizePort(daemonPort) ?? DEFAULT_DAEMON_PORT;
|
|
165
165
|
return port === DEFAULT_DAEMON_PORT ? host : `${host}:${port}`;
|
|
166
166
|
}
|
|
167
|
+
|
|
168
|
+
export function extractSubnetPrefix(ip: string): string | null {
|
|
169
|
+
if (!isValidIpv4(ip)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const parts = ip.split('.');
|
|
173
|
+
return `${parts[0]}.${parts[1]}.${parts[2]}.`;
|
|
174
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { DevConnectTab } from './DevConnectTab';
|
|
2
|
-
import { isCameraKitAvailable } from './cameraKit';
|
|
3
2
|
import { loadDevConnectPreferences } from './devConnectPreferences';
|
|
4
|
-
import { DEFAULT_DAEMON_PORT, DEFAULT_METRO_PORT } from './devConnectUtils';
|
|
5
|
-
import { isNativeDevConnectAvailable } from './nativeDevConnect';
|
|
3
|
+
import { DEFAULT_DAEMON_PORT, DEFAULT_METRO_PORT, extractSubnetPrefix } from './devConnectUtils';
|
|
4
|
+
import { getDeviceLocalIp, isNativeDevConnectAvailable } from './nativeDevConnect';
|
|
6
5
|
import { isSimulator } from './platformDetect';
|
|
7
6
|
import { daemonClient } from '../../utils/DaemonClient';
|
|
8
7
|
import type { DebugFeature, DebugFeatureListener } from '../../types';
|
|
9
|
-
import type { DevConnectState } from './types';
|
|
8
|
+
import type { DevConnectFeatureControls, DevConnectSettingsPatch, DevConnectState } from './types';
|
|
10
9
|
|
|
11
10
|
export type { DevConnectState } from './types';
|
|
12
11
|
export {
|
|
@@ -24,6 +23,7 @@ export {
|
|
|
24
23
|
saveDaemonPort,
|
|
25
24
|
saveMetroPort,
|
|
26
25
|
} from './devConnectPreferences';
|
|
26
|
+
export { nativeIsDebugBuild } from './nativeDevConnect';
|
|
27
27
|
|
|
28
28
|
export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
29
29
|
const listeners = new Set<DebugFeatureListener>();
|
|
@@ -32,7 +32,6 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
32
32
|
computerHost: '',
|
|
33
33
|
metroPort: DEFAULT_METRO_PORT,
|
|
34
34
|
daemonPort: DEFAULT_DAEMON_PORT,
|
|
35
|
-
qrAvailable: isCameraKitAvailable(),
|
|
36
35
|
nativeMetroAvailable: isNativeDevConnectAvailable(),
|
|
37
36
|
streaming: daemonClient.isConnected(),
|
|
38
37
|
};
|
|
@@ -45,12 +44,21 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
45
44
|
listeners.forEach((listener) => listener());
|
|
46
45
|
};
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
const updateSettings = (patch: DevConnectSettingsPatch) => {
|
|
48
|
+
state = {
|
|
49
|
+
...state,
|
|
50
|
+
...patch,
|
|
51
|
+
};
|
|
52
|
+
notify();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const feature: DebugFeature<DevConnectState> & DevConnectFeatureControls = {
|
|
49
56
|
name: 'devConnect',
|
|
50
57
|
label: 'DevConnect',
|
|
51
58
|
renderContent: DevConnectTab,
|
|
52
59
|
setup() {
|
|
53
|
-
|
|
60
|
+
daemonClient.setOnConnectionChange(() => notify());
|
|
61
|
+
loadDevConnectPreferences().then(async (preferences) => {
|
|
54
62
|
state = {
|
|
55
63
|
...state,
|
|
56
64
|
computerHost: preferences.computerHost,
|
|
@@ -58,6 +66,19 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
58
66
|
daemonPort: preferences.daemonPort,
|
|
59
67
|
nativeMetroAvailable: isNativeDevConnectAvailable(),
|
|
60
68
|
};
|
|
69
|
+
|
|
70
|
+
if (!state.isSimulator) {
|
|
71
|
+
try {
|
|
72
|
+
const localIp = await getDeviceLocalIp();
|
|
73
|
+
if (localIp) {
|
|
74
|
+
const prefix = extractSubnetPrefix(localIp);
|
|
75
|
+
if (prefix) {
|
|
76
|
+
state = { ...state, subnetPrefix: prefix };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch { /* subnetPrefix stays undefined */ }
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
notify();
|
|
62
83
|
}).catch(() => {
|
|
63
84
|
notify();
|
|
@@ -73,5 +94,8 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
73
94
|
listeners.delete(listener);
|
|
74
95
|
};
|
|
75
96
|
},
|
|
97
|
+
updateSettings,
|
|
76
98
|
};
|
|
99
|
+
|
|
100
|
+
return feature;
|
|
77
101
|
};
|
|
@@ -6,6 +6,8 @@ interface DebugToolkitDevConnectNativeModule {
|
|
|
6
6
|
applyMetroHost: (hostPort: string) => Promise<{ hostPort?: string } | void>;
|
|
7
7
|
resetMetroHost: () => Promise<void>;
|
|
8
8
|
getMetroHost?: () => Promise<string | null>;
|
|
9
|
+
getLocalIp?: () => Promise<string | null>;
|
|
10
|
+
isDebugBuild?: () => Promise<boolean>;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
type MetroBundleFailureReason =
|
|
@@ -124,3 +126,29 @@ export async function resetMetroBundle(): Promise<MetroBundleResult | { ok: true
|
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
|
|
130
|
+
export async function getDeviceLocalIp(): Promise<string | null> {
|
|
131
|
+
const nativeModule = getNativeModule();
|
|
132
|
+
if (!nativeModule?.getLocalIp) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const ip = await nativeModule.getLocalIp();
|
|
137
|
+
return typeof ip === 'string' ? ip : null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function nativeIsDebugBuild(): Promise<boolean | null> {
|
|
144
|
+
const nativeModule = getNativeModule();
|
|
145
|
+
if (!nativeModule?.isDebugBuild) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const result = await nativeModule.isDebugBuild();
|
|
150
|
+
return typeof result === 'boolean' ? result : null;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -3,7 +3,15 @@ export interface DevConnectState {
|
|
|
3
3
|
computerHost: string;
|
|
4
4
|
metroPort: string;
|
|
5
5
|
daemonPort: string;
|
|
6
|
-
|
|
6
|
+
subnetPrefix?: string;
|
|
7
7
|
nativeMetroAvailable: boolean;
|
|
8
8
|
streaming: boolean;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
export type DevConnectSettingsPatch = Partial<
|
|
12
|
+
Pick<DevConnectState, 'computerHost' | 'metroPort' | 'daemonPort'>
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export interface DevConnectFeatureControls {
|
|
16
|
+
updateSettings?: (patch: DevConnectSettingsPatch) => void;
|
|
17
|
+
}
|
package/src/ui/DebugView.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { DebugToolkitProvider } from '../core/DebugToolkitProvider';
|
|
3
3
|
import { initializeDebugToolkit } from '../core/initialize';
|
|
4
4
|
import type { FeatureConfigs } from '../core/initialize';
|
|
@@ -42,6 +42,8 @@ export function DebugView({
|
|
|
42
42
|
environments,
|
|
43
43
|
enabled,
|
|
44
44
|
}: DebugViewProps) {
|
|
45
|
+
const destroyRef = useRef<(() => void) | null>(null);
|
|
46
|
+
|
|
45
47
|
useEffect(() => {
|
|
46
48
|
// Build feature config: all enabled by default, user overrides take precedence
|
|
47
49
|
const resolvedFeatures: FeatureConfigs = {
|
|
@@ -61,13 +63,20 @@ export function DebugView({
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// Initialize toolkit
|
|
64
|
-
|
|
66
|
+
let cancelled = false;
|
|
67
|
+
initializeDebugToolkit({
|
|
65
68
|
features: resolvedFeatures,
|
|
66
69
|
enabled,
|
|
70
|
+
}).then((toolkit) => {
|
|
71
|
+
if (!cancelled) {
|
|
72
|
+
destroyRef.current = () => toolkit.destroy();
|
|
73
|
+
}
|
|
67
74
|
});
|
|
68
75
|
|
|
69
76
|
return () => {
|
|
70
|
-
|
|
77
|
+
cancelled = true;
|
|
78
|
+
destroyRef.current?.();
|
|
79
|
+
destroyRef.current = null;
|
|
71
80
|
};
|
|
72
81
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
82
|
}, []);
|
|
@@ -186,6 +186,7 @@ export class DaemonClient {
|
|
|
186
186
|
private _onEndpointDetected: ((url: string) => void) | undefined;
|
|
187
187
|
private _restorePromise: Promise<void> | null = null;
|
|
188
188
|
private _sessionId: SessionInfo | null = null;
|
|
189
|
+
private _onConnectionChange: (() => void) | undefined;
|
|
189
190
|
|
|
190
191
|
constructor(options: DaemonClientOptions) {
|
|
191
192
|
this._fetch = options.fetch;
|
|
@@ -315,6 +316,7 @@ export class DaemonClient {
|
|
|
315
316
|
).remove;
|
|
316
317
|
|
|
317
318
|
this._stream = state;
|
|
319
|
+
this._onConnectionChange?.();
|
|
318
320
|
this.emitStatus({ state: 'connecting' });
|
|
319
321
|
this.enqueueSendFullReport();
|
|
320
322
|
}
|
|
@@ -324,6 +326,7 @@ export class DaemonClient {
|
|
|
324
326
|
const state = this._stream;
|
|
325
327
|
this._stream = null;
|
|
326
328
|
this._sessionId = null;
|
|
329
|
+
this._onConnectionChange?.();
|
|
327
330
|
|
|
328
331
|
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
329
332
|
if (state.retryTimer) clearTimeout(state.retryTimer);
|
|
@@ -360,6 +363,10 @@ export class DaemonClient {
|
|
|
360
363
|
this._onEndpointDetected = callback;
|
|
361
364
|
}
|
|
362
365
|
|
|
366
|
+
setOnConnectionChange(callback: (() => void) | undefined): void {
|
|
367
|
+
this._onConnectionChange = callback;
|
|
368
|
+
}
|
|
369
|
+
|
|
363
370
|
// --- Restore (init-time reconnect) ---
|
|
364
371
|
|
|
365
372
|
async restore(): Promise<void> {
|