react-native-debug-toolkit 3.2.0 → 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 +94 -26
- package/lib/commonjs/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/commonjs/features/devConnect/DevConnectTab.js +261 -75
- 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 +95 -27
- package/lib/module/features/devConnect/DevConnectQrScanner.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +265 -79
- 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 +90 -28
- package/src/features/devConnect/DevConnectTab.tsx +257 -55
- 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
|
@@ -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
|
}
|
|
@@ -1,21 +1,39 @@
|
|
|
1
|
-
const
|
|
1
|
+
export const DEFAULT_METRO_PORT = '8081';
|
|
2
|
+
export const DEFAULT_DAEMON_PORT = '3799';
|
|
2
3
|
|
|
3
4
|
export interface MetroUrls {
|
|
4
5
|
expUrl: string;
|
|
5
6
|
httpUrl: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
export interface MetroTarget {
|
|
10
|
+
host: string;
|
|
11
|
+
port: string;
|
|
12
|
+
hostPort: string;
|
|
13
|
+
statusUrl: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ParsedComputerTarget {
|
|
17
|
+
computerHost: string;
|
|
18
|
+
metroPort: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export interface ParsedMetroQrPayload {
|
|
9
22
|
computerHost: string;
|
|
23
|
+
metroPort: string;
|
|
10
24
|
source: string;
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
function isValidIpv4(host: string): boolean {
|
|
14
28
|
const parts = host.split('.');
|
|
15
|
-
if (parts.length !== 4)
|
|
29
|
+
if (parts.length !== 4) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
16
32
|
|
|
17
33
|
return parts.every((part) => {
|
|
18
|
-
if (!/^\d{1,3}$/.test(part))
|
|
34
|
+
if (!/^\d{1,3}$/.test(part)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
19
37
|
const value = Number(part);
|
|
20
38
|
return value >= 0 && value <= 255 && String(value) === part;
|
|
21
39
|
});
|
|
@@ -29,31 +47,120 @@ function toUrlInput(raw: string): string {
|
|
|
29
47
|
return `http://${trimmed}`;
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
function parseHostAndPort(raw: string): { host: string; port: string } | null {
|
|
33
51
|
const trimmed = raw.trim();
|
|
34
|
-
if (!trimmed)
|
|
52
|
+
if (!trimmed) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
35
55
|
|
|
36
56
|
try {
|
|
37
57
|
const parsed = new URL(toUrlInput(trimmed));
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
return {
|
|
59
|
+
host: parsed.hostname.trim(),
|
|
60
|
+
port: parsed.port.trim(),
|
|
61
|
+
};
|
|
40
62
|
} catch {
|
|
41
63
|
return null;
|
|
42
64
|
}
|
|
43
65
|
}
|
|
44
66
|
|
|
45
|
-
export function
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
67
|
+
export function normalizeComputerHost(raw: string): string | null {
|
|
68
|
+
const parsed = parseHostAndPort(raw);
|
|
69
|
+
if (!parsed) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return isValidIpv4(parsed.host) ? parsed.host : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizePort(raw: string): string | null {
|
|
77
|
+
const trimmed = raw.trim();
|
|
78
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const value = Number(trimmed);
|
|
83
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return String(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseComputerTarget(raw: string): ParsedComputerTarget | null {
|
|
90
|
+
const parsed = parseHostAndPort(raw);
|
|
91
|
+
if (!parsed || !isValidIpv4(parsed.host)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const metroPort = parsed.port
|
|
96
|
+
? normalizePort(parsed.port)
|
|
97
|
+
: DEFAULT_METRO_PORT;
|
|
98
|
+
if (!metroPort) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
computerHost: parsed.host,
|
|
104
|
+
metroPort,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeMetroHost(rawHost: string): string | null {
|
|
109
|
+
const parsed = parseHostAndPort(rawHost);
|
|
110
|
+
if (!parsed) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (parsed.host === 'localhost') {
|
|
114
|
+
return parsed.host;
|
|
115
|
+
}
|
|
116
|
+
return isValidIpv4(parsed.host) ? parsed.host : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function buildMetroTarget(rawHost: string, rawPort = DEFAULT_METRO_PORT): MetroTarget | null {
|
|
120
|
+
const host = normalizeMetroHost(rawHost);
|
|
121
|
+
if (!host) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const port = normalizePort(rawPort);
|
|
126
|
+
if (!port) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
host,
|
|
132
|
+
port,
|
|
133
|
+
hostPort: `${host}:${port}`,
|
|
134
|
+
statusUrl: `http://${host}:${port}/status`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function buildMetroUrls(rawHost: string, rawPort = DEFAULT_METRO_PORT): MetroUrls | null {
|
|
139
|
+
const target = buildMetroTarget(rawHost, rawPort);
|
|
140
|
+
if (!target) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
48
143
|
|
|
49
144
|
return {
|
|
50
|
-
expUrl: `exp://${
|
|
51
|
-
httpUrl: `http://${
|
|
145
|
+
expUrl: `exp://${target.hostPort}`,
|
|
146
|
+
httpUrl: `http://${target.hostPort}`,
|
|
52
147
|
};
|
|
53
148
|
}
|
|
54
149
|
|
|
55
150
|
export function parseMetroQrPayload(payload: string): ParsedMetroQrPayload | null {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
58
|
-
|
|
151
|
+
const target = parseComputerTarget(payload);
|
|
152
|
+
if (!target) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return { ...target, source: payload };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function buildDaemonDeviceHost(computerHost: string, daemonPort: string): string {
|
|
159
|
+
const host = normalizeComputerHost(computerHost);
|
|
160
|
+
if (!host) {
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const port = normalizePort(daemonPort) ?? DEFAULT_DAEMON_PORT;
|
|
165
|
+
return port === DEFAULT_DAEMON_PORT ? host : `${host}:${port}`;
|
|
59
166
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DevConnectTab } from './DevConnectTab';
|
|
2
2
|
import { isCameraKitAvailable } from './cameraKit';
|
|
3
3
|
import { loadDevConnectPreferences } from './devConnectPreferences';
|
|
4
|
+
import { DEFAULT_DAEMON_PORT, DEFAULT_METRO_PORT } from './devConnectUtils';
|
|
5
|
+
import { isNativeDevConnectAvailable } from './nativeDevConnect';
|
|
4
6
|
import { isSimulator } from './platformDetect';
|
|
5
7
|
import { daemonClient } from '../../utils/DaemonClient';
|
|
6
8
|
import type { DebugFeature, DebugFeatureListener } from '../../types';
|
|
@@ -10,12 +12,17 @@ export type { DevConnectState } from './types';
|
|
|
10
12
|
export {
|
|
11
13
|
buildMetroUrls,
|
|
12
14
|
normalizeComputerHost,
|
|
15
|
+
normalizePort,
|
|
16
|
+
parseComputerTarget,
|
|
13
17
|
parseMetroQrPayload,
|
|
14
18
|
} from './devConnectUtils';
|
|
15
19
|
export {
|
|
16
20
|
loadDevConnectPreferences,
|
|
17
21
|
restoreDevConnectSettingsToDaemon,
|
|
18
22
|
saveComputerHost,
|
|
23
|
+
saveComputerTarget,
|
|
24
|
+
saveDaemonPort,
|
|
25
|
+
saveMetroPort,
|
|
19
26
|
} from './devConnectPreferences';
|
|
20
27
|
|
|
21
28
|
export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
@@ -23,7 +30,10 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
23
30
|
let state: DevConnectState = {
|
|
24
31
|
isSimulator: isSimulator(),
|
|
25
32
|
computerHost: '',
|
|
33
|
+
metroPort: DEFAULT_METRO_PORT,
|
|
34
|
+
daemonPort: DEFAULT_DAEMON_PORT,
|
|
26
35
|
qrAvailable: isCameraKitAvailable(),
|
|
36
|
+
nativeMetroAvailable: isNativeDevConnectAvailable(),
|
|
27
37
|
streaming: daemonClient.isConnected(),
|
|
28
38
|
};
|
|
29
39
|
|
|
@@ -44,6 +54,9 @@ export const createDevConnectFeature = (): DebugFeature<DevConnectState> => {
|
|
|
44
54
|
state = {
|
|
45
55
|
...state,
|
|
46
56
|
computerHost: preferences.computerHost,
|
|
57
|
+
metroPort: preferences.metroPort,
|
|
58
|
+
daemonPort: preferences.daemonPort,
|
|
59
|
+
nativeMetroAvailable: isNativeDevConnectAvailable(),
|
|
47
60
|
};
|
|
48
61
|
notify();
|
|
49
62
|
}).catch(() => {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { DevSettings, NativeModules } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { buildMetroTarget } from './devConnectUtils';
|
|
4
|
+
|
|
5
|
+
interface DebugToolkitDevConnectNativeModule {
|
|
6
|
+
applyMetroHost: (hostPort: string) => Promise<{ hostPort?: string } | void>;
|
|
7
|
+
resetMetroHost: () => Promise<void>;
|
|
8
|
+
getMetroHost?: () => Promise<string | null>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type MetroBundleFailureReason =
|
|
12
|
+
| 'invalid_target'
|
|
13
|
+
| 'native_unavailable'
|
|
14
|
+
| 'fetch_unavailable'
|
|
15
|
+
| 'metro_unreachable'
|
|
16
|
+
| 'native_error';
|
|
17
|
+
|
|
18
|
+
export type MetroBundleResult =
|
|
19
|
+
| { ok: true; hostPort: string }
|
|
20
|
+
| { ok: false; reason: MetroBundleFailureReason; error?: string; statusUrl?: string };
|
|
21
|
+
|
|
22
|
+
type FetchLike = (
|
|
23
|
+
url: string,
|
|
24
|
+
init: { method: string },
|
|
25
|
+
) => Promise<{
|
|
26
|
+
ok?: boolean;
|
|
27
|
+
text?: () => Promise<string>;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
function getNativeModule(): DebugToolkitDevConnectNativeModule | null {
|
|
31
|
+
const nativeModule = NativeModules.DebugToolkitDevConnect as Partial<DebugToolkitDevConnectNativeModule> | undefined;
|
|
32
|
+
if (
|
|
33
|
+
nativeModule &&
|
|
34
|
+
typeof nativeModule.applyMetroHost === 'function' &&
|
|
35
|
+
typeof nativeModule.resetMetroHost === 'function'
|
|
36
|
+
) {
|
|
37
|
+
return nativeModule as DebugToolkitDevConnectNativeModule;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isNativeDevConnectAvailable(): boolean {
|
|
43
|
+
return getNativeModule() !== null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function checkMetroStatus(statusUrl: string): Promise<MetroBundleResult | null> {
|
|
47
|
+
const fetchImpl = globalThis.fetch as FetchLike | undefined;
|
|
48
|
+
if (!fetchImpl) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
reason: 'fetch_unavailable',
|
|
52
|
+
statusUrl,
|
|
53
|
+
error: 'global fetch is not available',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetchImpl(statusUrl, { method: 'GET' });
|
|
59
|
+
const body = typeof response.text === 'function' ? await response.text() : '';
|
|
60
|
+
if (response.ok === false || !body.includes('packager-status:running')) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: 'metro_unreachable',
|
|
64
|
+
statusUrl,
|
|
65
|
+
error: body || 'Metro status endpoint did not report running',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
reason: 'metro_unreachable',
|
|
73
|
+
statusUrl,
|
|
74
|
+
error: error instanceof Error ? error.message : String(error),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function applyMetroBundle(host: string, port: string): Promise<MetroBundleResult> {
|
|
80
|
+
const target = buildMetroTarget(host, port);
|
|
81
|
+
if (!target) {
|
|
82
|
+
return { ok: false, reason: 'invalid_target' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const nativeModule = getNativeModule();
|
|
86
|
+
if (!nativeModule) {
|
|
87
|
+
return { ok: false, reason: 'native_unavailable' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const statusError = await checkMetroStatus(target.statusUrl);
|
|
91
|
+
if (statusError) {
|
|
92
|
+
return statusError;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await nativeModule.applyMetroHost(target.hostPort);
|
|
97
|
+
DevSettings.reload?.('DebugToolkit DevConnect Metro host changed');
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
hostPort: result && typeof result.hostPort === 'string' ? result.hostPort : target.hostPort,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
reason: 'native_error',
|
|
106
|
+
error: error instanceof Error ? error.message : String(error),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function resetMetroBundle(): Promise<MetroBundleResult | { ok: true }> {
|
|
112
|
+
const nativeModule = getNativeModule();
|
|
113
|
+
if (!nativeModule) {
|
|
114
|
+
return { ok: false, reason: 'native_unavailable' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await nativeModule.resetMetroHost();
|
|
119
|
+
DevSettings.reload?.('DebugToolkit DevConnect Metro host reset');
|
|
120
|
+
return { ok: true };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
reason: 'native_error',
|
|
125
|
+
error: error instanceof Error ? error.message : String(error),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
2
|
|
|
3
3
|
export function isSimulator(): boolean {
|
|
4
4
|
const { OS } = Platform;
|
|
5
|
+
const constants = (Platform.constants ?? {}) as Record<string, unknown>;
|
|
5
6
|
|
|
6
7
|
if (OS === 'android') {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
if (constants.isEmulator === true) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const model = String(constants.Model ?? constants.model ?? '').toLowerCase();
|
|
10
12
|
return model.includes('sdk') || model.includes('emulator') || model.includes('google_sdk');
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
if (OS === 'ios') {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const deviceInfo = NativeModules.DeviceInfo
|
|
17
|
-
?? NativeModules.PlatformConstants;
|
|
18
|
-
if (deviceInfo) {
|
|
19
|
-
const model = String(deviceInfo.model ?? '').toLowerCase();
|
|
20
|
-
if (model.includes('simulator')) return true;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
16
|
+
const model = String(constants.model ?? '').toLowerCase();
|
|
17
|
+
return model.includes('simulator');
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
return false;
|
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
1
|
type AsyncStorageLike = { getItem: (k: string) => Promise<string | null>; setItem: (k: string, v: string) => Promise<void> };
|
|
2
|
+
type NativePreferencesLike = { getPreference: (k: string) => Promise<string | null>; setPreference: (k: string, v: string) => Promise<void> };
|
|
3
3
|
|
|
4
4
|
const memoryStore = new Map<string, string>();
|
|
5
5
|
|
|
6
6
|
function loadAsyncStorage(): AsyncStorageLike | null {
|
|
7
7
|
try {
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9
8
|
const mod = require('@react-native-async-storage/async-storage');
|
|
10
|
-
if (mod && typeof mod.getItem === 'function')
|
|
9
|
+
if (mod && typeof mod.getItem === 'function') {
|
|
10
|
+
return mod;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function loadNativePreferences(): NativePreferencesLike | null {
|
|
19
|
+
try {
|
|
20
|
+
const { NativeModules } = require('react-native') as { NativeModules?: { DebugToolkitDevConnect?: Partial<NativePreferencesLike> } };
|
|
21
|
+
const mod = NativeModules?.DebugToolkitDevConnect;
|
|
22
|
+
if (
|
|
23
|
+
mod &&
|
|
24
|
+
typeof mod.getPreference === 'function' &&
|
|
25
|
+
typeof mod.setPreference === 'function'
|
|
26
|
+
) {
|
|
27
|
+
return mod as NativePreferencesLike;
|
|
28
|
+
}
|
|
11
29
|
return null;
|
|
12
30
|
} catch {
|
|
13
31
|
return null;
|
|
@@ -20,6 +38,16 @@ export async function setPreference(key: string, value: string): Promise<void> {
|
|
|
20
38
|
if (AsyncStorage) {
|
|
21
39
|
try {
|
|
22
40
|
await AsyncStorage.setItem(key, value);
|
|
41
|
+
return;
|
|
42
|
+
} catch {
|
|
43
|
+
// degrade to memory only
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nativePreferences = loadNativePreferences();
|
|
48
|
+
if (nativePreferences) {
|
|
49
|
+
try {
|
|
50
|
+
await nativePreferences.setPreference(key, value);
|
|
23
51
|
} catch {
|
|
24
52
|
// degrade to memory only
|
|
25
53
|
}
|
|
@@ -31,11 +59,26 @@ export async function getPreference(key: string): Promise<string | null> {
|
|
|
31
59
|
if (AsyncStorage) {
|
|
32
60
|
try {
|
|
33
61
|
const val = await AsyncStorage.getItem(key);
|
|
34
|
-
if (val !== null)
|
|
62
|
+
if (val !== null) {
|
|
63
|
+
return val;
|
|
64
|
+
}
|
|
35
65
|
} catch {
|
|
36
66
|
// fall through to memory
|
|
37
67
|
}
|
|
38
68
|
}
|
|
69
|
+
|
|
70
|
+
const nativePreferences = loadNativePreferences();
|
|
71
|
+
if (nativePreferences) {
|
|
72
|
+
try {
|
|
73
|
+
const val = await nativePreferences.getPreference(key);
|
|
74
|
+
if (val !== null) {
|
|
75
|
+
return val;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// fall through to memory
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
return memoryStore.get(key) ?? null;
|
|
40
83
|
}
|
|
41
84
|
|
|
@@ -46,4 +89,6 @@ export const KEYS = {
|
|
|
46
89
|
networkLogs: '@react_native_debug_toolkit/network_logs',
|
|
47
90
|
trackLogs: '@react_native_debug_toolkit/track_logs',
|
|
48
91
|
computerHost: '@react_native_debug_toolkit/computer_host',
|
|
92
|
+
metroPort: '@react_native_debug_toolkit/metro_port',
|
|
93
|
+
daemonPort: '@react_native_debug_toolkit/daemon_port',
|
|
49
94
|
} as const;
|