rnxsim 0.1.345 → 0.1.347
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 +9 -3
- package/cli/commands/welcome.ts +19 -17
- package/cli/outbound-endpoints.ts +1 -1
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-52G55XTO.js → agent-VVY2A5YV.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-BOZM43ON.js → agent-wrapper-YBMTZ3KU.js} +2 -2
- package/dist-cli/chunks/{app-fonts-A2L6V5EZ.js → app-fonts-EF7FM3VP.js} +2 -2
- package/dist-cli/chunks/{assert-CJKIP4LN.js → assert-J7N6WGMS.js} +2 -2
- package/dist-cli/chunks/{auth-VHHN3TOB.js → auth-YGX3WVEL.js} +2 -2
- package/dist-cli/chunks/{beta-EBDIGV3E.js → beta-JXYFAOHO.js} +2 -2
- package/dist-cli/chunks/{camera-5F4KKCQ3.js → camera-WIVF3UQ3.js} +2 -2
- package/dist-cli/chunks/{chunk-PLB6UVVX.js → chunk-23WNLQUB.js} +2 -2
- package/dist-cli/chunks/{chunk-KZFJNDH3.js → chunk-24Q4J4U6.js} +1 -1
- package/dist-cli/chunks/chunk-2E5KDIGN.js +14 -0
- package/dist-cli/chunks/{chunk-XJBFJGNT.js → chunk-2PAG4FBP.js} +2 -2
- package/dist-cli/chunks/{chunk-7LGINKM6.js → chunk-2UZ6QQ6S.js} +2 -2
- package/dist-cli/chunks/{chunk-7V4KPZ7O.js → chunk-2W4ME6YI.js} +2 -2
- package/dist-cli/chunks/{chunk-5GXRRUOA.js → chunk-3HBHHU5F.js} +3 -3
- package/dist-cli/chunks/{chunk-E2RGSCHT.js → chunk-3SPBSEVL.js} +1 -1
- package/dist-cli/chunks/{chunk-QNBBKRCA.js → chunk-4KK5IRA6.js} +2 -2
- package/dist-cli/chunks/{chunk-4OKV7T5D.js → chunk-4KOWLH77.js} +2 -2
- package/dist-cli/chunks/{chunk-DPP2M4AJ.js → chunk-4RI6KXVH.js} +1 -1
- package/dist-cli/chunks/{chunk-DHM3JDCK.js → chunk-6CGQWXXT.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMTZ5G5H.js → chunk-7C6XE6AT.js} +2 -2
- package/dist-cli/chunks/{chunk-7YMQRA6L.js → chunk-7D56NCJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-4ZILDTZX.js → chunk-ABBCVE5V.js} +2 -2
- package/dist-cli/chunks/{chunk-JC33TOAF.js → chunk-ANANQDHB.js} +1 -1
- package/dist-cli/chunks/{chunk-BFTDL2GX.js → chunk-BA45ZZKU.js} +2 -2
- package/dist-cli/chunks/{chunk-RB4VFUNM.js → chunk-BAXXUHKV.js} +2 -2
- package/dist-cli/chunks/{chunk-LAQGSSTT.js → chunk-DC4MQAR3.js} +1 -1
- package/dist-cli/chunks/{chunk-KA4PJNTE.js → chunk-DTNRKFSE.js} +2 -2
- package/dist-cli/chunks/{chunk-A2Q76LJV.js → chunk-EEZEVA2C.js} +2 -2
- package/dist-cli/chunks/{chunk-7UIIAHVU.js → chunk-EIIZZ2XK.js} +2 -2
- package/dist-cli/chunks/{chunk-TEU5NGUA.js → chunk-FQ3OHHCY.js} +2 -2
- package/dist-cli/chunks/{chunk-BCQV63BW.js → chunk-HWAGFGV7.js} +3 -3
- package/dist-cli/chunks/{chunk-XXZLK4BY.js → chunk-HY7XOD5E.js} +1 -1
- package/dist-cli/chunks/{chunk-T7SBFG5I.js → chunk-IQWQ6KI7.js} +2 -2
- package/dist-cli/chunks/{chunk-TMRD5MHL.js → chunk-IS2LT6MN.js} +2 -2
- package/dist-cli/chunks/{chunk-NUPRHZQW.js → chunk-JG2TIZ7E.js} +2 -2
- package/dist-cli/chunks/{chunk-YVSUFVVB.js → chunk-JKRVWCTC.js} +1 -1
- package/dist-cli/chunks/{chunk-3DBTS4MC.js → chunk-JP4FCK5J.js} +6 -6
- package/dist-cli/chunks/{chunk-RTDHX6CW.js → chunk-JQTBZERT.js} +2 -2
- package/dist-cli/chunks/{chunk-OPHT4DUO.js → chunk-KYTBJ2QV.js} +1 -1
- package/dist-cli/chunks/{chunk-63ASUHOV.js → chunk-LRRQI4TP.js} +2 -2
- package/dist-cli/chunks/{chunk-RT3DXTDP.js → chunk-LWE2HRS4.js} +2 -2
- package/dist-cli/chunks/{chunk-U5YHXQHH.js → chunk-NJ2JVAGV.js} +2 -2
- package/dist-cli/chunks/{chunk-4BIPA7MD.js → chunk-O7WPHIYQ.js} +1 -1
- package/dist-cli/chunks/{chunk-WPPGU5IK.js → chunk-OG3KGU4V.js} +2 -2
- package/dist-cli/chunks/{chunk-KH777UAO.js → chunk-PCU2CQCI.js} +3 -3
- package/dist-cli/chunks/{chunk-KRZPFXVN.js → chunk-QJYUHYQS.js} +1 -1
- package/dist-cli/chunks/{chunk-HK7YKH6N.js → chunk-QK2U5IW3.js} +2 -2
- package/dist-cli/chunks/{chunk-3N7M5HMJ.js → chunk-SMLCC5EP.js} +1 -1
- package/dist-cli/chunks/{chunk-QNRWPAOA.js → chunk-SNBSRIKO.js} +2 -2
- package/dist-cli/chunks/{chunk-R7ZNRGQN.js → chunk-SYQZTZTR.js} +1 -1
- package/dist-cli/chunks/{chunk-7GO2DSYQ.js → chunk-TAYE2YII.js} +1 -1
- package/dist-cli/chunks/{chunk-EPLVIDPC.js → chunk-U6CJ3EHB.js} +1 -1
- package/dist-cli/chunks/{chunk-GKGXULJK.js → chunk-UM7IG4DE.js} +2 -2
- package/dist-cli/chunks/{chunk-VLBN3KRC.js → chunk-VENH5KLL.js} +1 -1
- package/dist-cli/chunks/{chunk-P2CGGMU2.js → chunk-W4MASUQ7.js} +2 -2
- package/dist-cli/chunks/{chunk-WU72KL6Z.js → chunk-WIYBEKBM.js} +2 -2
- package/dist-cli/chunks/{chunk-LRM2XCEQ.js → chunk-WNZOJDBL.js} +1 -1
- package/dist-cli/chunks/{chunk-UVOEHIGP.js → chunk-XB7DLZNQ.js} +1 -1
- package/dist-cli/chunks/{chunk-PLUWN2V5.js → chunk-XGUCAKZM.js} +1 -1
- package/dist-cli/chunks/{chunk-DGTV3KOW.js → chunk-Z6K6TD7X.js} +1 -1
- package/dist-cli/chunks/{chunk-BHKSCNDC.js → chunk-ZPCQFQEN.js} +1 -1
- package/dist-cli/chunks/{chunk-HXE344V3.js → chunk-ZY44NCRQ.js} +1 -1
- package/dist-cli/chunks/{cleanup-MAVZJXVW.js → cleanup-ZIDQQLEN.js} +2 -2
- package/dist-cli/chunks/cli-version-BEJJXF35.js +4 -0
- package/dist-cli/chunks/{compat-7AURLGCS.js → compat-5PFPTBBK.js} +2 -2
- package/dist-cli/chunks/{config-TN3Y4J5N.js → config-CG73FOTM.js} +2 -2
- package/dist-cli/chunks/{control-EV2ELU5R.js → control-7IYIDOOI.js} +2 -2
- package/dist-cli/chunks/daemon-G6VR7LJI.js +4 -0
- package/dist-cli/chunks/{debug-JICITUB6.js → debug-6AQQXXE3.js} +3 -3
- package/dist-cli/chunks/{desktop-4F5HGLLX.js → desktop-HJE5B4XO.js} +3 -3
- package/dist-cli/chunks/{detox-ZC7NHLJI.js → detox-KUZ7EYOP.js} +2 -2
- package/dist-cli/chunks/{device-V57GHRXX.js → device-O6WI4PQT.js} +2 -2
- package/dist-cli/chunks/{diagnose-FF4IWYXC.js → diagnose-FTCJGH2Q.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-T277L6ZO.js → disk-cleanup-YAXG52EZ.js} +2 -2
- package/dist-cli/chunks/drivers-6QOL2SON.js +4 -0
- package/dist-cli/chunks/{film-G7ZAPWB7.js → film-KTLKRCJR.js} +3 -3
- package/dist-cli/chunks/flow-6VQN4VNW.js +4 -0
- package/dist-cli/chunks/help-22OURA2E.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-VSJNNSNI.js → hidden-runtime-alias-W375XBOM.js} +2 -2
- package/dist-cli/chunks/{home-paths-75HP3HKA.js → home-paths-CIGRWLDB.js} +2 -2
- package/dist-cli/chunks/inspect-36A2IEJL.js +4 -0
- package/dist-cli/chunks/install-desktop-KOW3XXUF.js +4 -0
- package/dist-cli/chunks/{login-ZSIHGHUK.js → login-AJWLXLPQ.js} +2 -2
- package/dist-cli/chunks/{logout-Q4DXMI2C.js → logout-CEJLZIOI.js} +2 -2
- package/dist-cli/chunks/{maestro-LE5LCRBL.js → maestro-YF4M6M6L.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-D3ZIJCRL.js → maestro-generate-FJWDLMEB.js} +3 -3
- package/dist-cli/chunks/{mode-BWX6MDPJ.js → mode-6BJ523BY.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-6CVASL3S.js → optional-demo-registry-5CKCN2TP.js} +2 -2
- package/dist-cli/chunks/{perf-NXQZISN6.js → perf-WE6QTFJU.js} +2 -2
- package/dist-cli/chunks/{permissions-5PD67RRO.js → permissions-OZZZZFPB.js} +2 -2
- package/dist-cli/chunks/privacy-3BERNBUM.js +4 -0
- package/dist-cli/chunks/{record-YJF4KXMR.js → record-QHQS2524.js} +3 -3
- package/dist-cli/chunks/{report-issue-XOL5FQBG.js → report-issue-SEMVVJUH.js} +2 -2
- package/dist-cli/chunks/reset-A5AA5ML6.js +4 -0
- package/dist-cli/chunks/runtime-R26QWA2M.js +4 -0
- package/dist-cli/chunks/{screenshot-command-VRPJXWFS.js → screenshot-command-WW33MAB3.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-QP4WA2F5.js → screenshot-layers-2GJTEQ4T.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-O2IBAVSP.js → screenshots-capture-J5HKONCC.js} +2 -2
- package/dist-cli/chunks/{serve-WC6RZNJL.js → serve-FEBNDKUD.js} +2 -2
- package/dist-cli/chunks/{setup-A6BIG3KL.js → setup-NGZWS373.js} +2 -2
- package/dist-cli/chunks/{skills-A7YC6RL5.js → skills-CFCZFEME.js} +2 -2
- package/dist-cli/chunks/{state-IJI5YROJ.js → state-XWRKK7FW.js} +2 -2
- package/dist-cli/chunks/{storage-Z2DBO2PQ.js → storage-C5GNK4XB.js} +2 -2
- package/dist-cli/chunks/store-L3A4CLND.js +4 -0
- package/dist-cli/chunks/telemetry-O67J22QU.js +4 -0
- package/dist-cli/chunks/{timeline-N7V2M6QA.js → timeline-7RA7ZILQ.js} +2 -2
- package/dist-cli/chunks/{upgrade-P2NH3BOR.js → upgrade-ZW6IJ5LX.js} +2 -2
- package/dist-cli/chunks/upload-UKUN6HCD.js +4 -0
- package/dist-cli/chunks/{version-OUHCSCUS.js → version-N3E4OIMM.js} +2 -2
- package/dist-cli/chunks/{web-6NRTF3UV.js → web-UC2SFEBA.js} +2 -2
- package/dist-cli/chunks/welcome-DAZPCMES.js +5 -0
- package/dist-cli/chunks/{what-happened-FUEL3BEF.js → what-happened-VUNKID5W.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +13 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +26 -10
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +101 -58
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +106 -59
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +127 -72
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +101 -47
- package/src/backend-origin.ts +15 -1
- package/src/dev-bundle-resolution.ts +29 -12
- package/dist-cli/chunks/chunk-TOF5TA5X.js +0 -14
- package/dist-cli/chunks/cli-version-2B4KRNML.js +0 -4
- package/dist-cli/chunks/daemon-KKDG5MYR.js +0 -4
- package/dist-cli/chunks/drivers-LCX7S76X.js +0 -4
- package/dist-cli/chunks/flow-ONZGGFGD.js +0 -4
- package/dist-cli/chunks/help-DROVDXJM.js +0 -4
- package/dist-cli/chunks/inspect-MDA7ESGQ.js +0 -4
- package/dist-cli/chunks/install-desktop-EIVZHIFA.js +0 -4
- package/dist-cli/chunks/privacy-H5KNRNIZ.js +0 -4
- package/dist-cli/chunks/reset-5LZN5P5B.js +0 -4
- package/dist-cli/chunks/runtime-U36VWBH3.js +0 -4
- package/dist-cli/chunks/store-BYTCKNIC.js +0 -4
- package/dist-cli/chunks/telemetry-JLHFMD7N.js +0 -4
- package/dist-cli/chunks/upload-Z2WX5DCD.js +0 -4
- package/dist-cli/chunks/welcome-6RS7PEJW.js +0 -5
|
@@ -22,6 +22,11 @@ import { promisify } from 'util'
|
|
|
22
22
|
import type { AppSettingsSpecifier } from '@sootsim/globals'
|
|
23
23
|
|
|
24
24
|
const execP = promisify(exec)
|
|
25
|
+
import {
|
|
26
|
+
buildLoopbackProbeBaseUrl,
|
|
27
|
+
LOOPBACK_PROBE_HOSTNAMES,
|
|
28
|
+
normalizeHostname,
|
|
29
|
+
} from '../src/backend-origin.ts'
|
|
25
30
|
import { applySootSimConfigToUrl } from '../src/config.ts'
|
|
26
31
|
import {
|
|
27
32
|
alignLoopbackBundleUrlWithProbe,
|
|
@@ -74,30 +79,42 @@ interface HttpResult {
|
|
|
74
79
|
statusCode: number
|
|
75
80
|
body: string
|
|
76
81
|
contentType?: string
|
|
82
|
+
hostname: string
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// cheap TCP-connect gate. lsof already says the socket is LISTEN, but the
|
|
80
86
|
// process can be closing, the fd can be a different protocol, or a zombie
|
|
81
87
|
// can linger — none of those cases are worth firing 5 HTTP probes at.
|
|
82
|
-
function tcpPing(port: number, timeout = TCP_GATE_MS): Promise<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
async function tcpPing(port: number, timeout = TCP_GATE_MS): Promise<string | null> {
|
|
89
|
+
try {
|
|
90
|
+
return await Promise.any(
|
|
91
|
+
LOOPBACK_PROBE_HOSTNAMES.map(
|
|
92
|
+
(hostname) =>
|
|
93
|
+
new Promise<string>((resolve, reject) => {
|
|
94
|
+
const sock = new net.Socket()
|
|
95
|
+
let settled = false
|
|
96
|
+
const done = (reachable: boolean) => {
|
|
97
|
+
if (settled) return
|
|
98
|
+
settled = true
|
|
99
|
+
sock.destroy()
|
|
100
|
+
if (reachable) resolve(hostname)
|
|
101
|
+
else reject(new Error(`unreachable loopback host ${hostname}`))
|
|
102
|
+
}
|
|
103
|
+
sock.setTimeout(timeout)
|
|
104
|
+
sock.once('connect', () => done(true))
|
|
105
|
+
sock.once('timeout', () => done(false))
|
|
106
|
+
sock.once('error', () => done(false))
|
|
107
|
+
sock.connect(port, hostname)
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
} catch {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
function httpGet(
|
|
117
|
+
hostname: string,
|
|
101
118
|
port: number,
|
|
102
119
|
path: string,
|
|
103
120
|
method: 'GET' | 'HEAD' = 'GET',
|
|
@@ -106,7 +123,7 @@ function httpGet(
|
|
|
106
123
|
): Promise<HttpResult | null> {
|
|
107
124
|
return new Promise((resolve) => {
|
|
108
125
|
const req = http.request(
|
|
109
|
-
{ hostname
|
|
126
|
+
{ hostname, port, path, method, timeout, headers },
|
|
110
127
|
(res) => {
|
|
111
128
|
let body = ''
|
|
112
129
|
res.on('data', (c: Buffer) => (body += c.toString()))
|
|
@@ -117,7 +134,12 @@ function httpGet(
|
|
|
117
134
|
return undefined
|
|
118
135
|
})()
|
|
119
136
|
res.on('end', () =>
|
|
120
|
-
resolve({
|
|
137
|
+
resolve({
|
|
138
|
+
statusCode: res.statusCode || 0,
|
|
139
|
+
body,
|
|
140
|
+
contentType,
|
|
141
|
+
hostname,
|
|
142
|
+
}),
|
|
121
143
|
)
|
|
122
144
|
},
|
|
123
145
|
)
|
|
@@ -342,6 +364,7 @@ export async function resolveProcessCwd(pid: number): Promise<string | null> {
|
|
|
342
364
|
// ── per-port probe orchestration ────────────────────────────────────────────
|
|
343
365
|
|
|
344
366
|
function makeResult(
|
|
367
|
+
hostname: string,
|
|
345
368
|
port: number,
|
|
346
369
|
framework: DiscoveredServer['framework'],
|
|
347
370
|
owner: Pick<DiscoveredServer, 'cwd' | 'pid'>,
|
|
@@ -351,10 +374,10 @@ function makeResult(
|
|
|
351
374
|
framework,
|
|
352
375
|
bundleUrl: withRuntimeConfig(
|
|
353
376
|
port,
|
|
354
|
-
|
|
377
|
+
`${buildLoopbackProbeBaseUrl('http:', hostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,
|
|
355
378
|
owner.cwd,
|
|
356
379
|
),
|
|
357
|
-
hmrUrl:
|
|
380
|
+
hmrUrl: `${buildLoopbackProbeBaseUrl('ws:', hostname, port)}/hot`,
|
|
358
381
|
lastSeen: Date.now(),
|
|
359
382
|
...owner,
|
|
360
383
|
}
|
|
@@ -408,7 +431,10 @@ function applyManifest(
|
|
|
408
431
|
const rawLaunchUrl = manifest?.launchAsset?.url
|
|
409
432
|
const launchUrl =
|
|
410
433
|
typeof rawLaunchUrl === 'string'
|
|
411
|
-
? alignLoopbackBundleUrlWithProbe(
|
|
434
|
+
? alignLoopbackBundleUrlWithProbe(
|
|
435
|
+
rawLaunchUrl,
|
|
436
|
+
buildLoopbackProbeBaseUrl('http:', manifestRes.hostname, result.port),
|
|
437
|
+
)
|
|
412
438
|
: rawLaunchUrl
|
|
413
439
|
if (launchUrl && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
|
|
414
440
|
result.bundleUrl = withRuntimeConfig(result.port, launchUrl, result.cwd)
|
|
@@ -423,14 +449,14 @@ function applyManifest(
|
|
|
423
449
|
} else {
|
|
424
450
|
const cleanPath = rawIconUrl.replace(/^\.\//, '')
|
|
425
451
|
result.iconUrl = buildIconProxyUrl(
|
|
426
|
-
|
|
452
|
+
`${buildLoopbackProbeBaseUrl('http:', manifestRes.hostname, result.port)}/assets/${cleanPath}`,
|
|
427
453
|
)
|
|
428
454
|
}
|
|
429
455
|
} else {
|
|
430
456
|
// no proxy requested — use the raw URL
|
|
431
457
|
result.iconUrl = rawIconUrl.startsWith('http')
|
|
432
458
|
? rawIconUrl
|
|
433
|
-
:
|
|
459
|
+
: `${buildLoopbackProbeBaseUrl('http:', manifestRes.hostname, result.port)}/assets/${rawIconUrl.replace(/^\.\//, '')}`
|
|
434
460
|
}
|
|
435
461
|
}
|
|
436
462
|
} catch {}
|
|
@@ -441,7 +467,11 @@ export function __applyManifestForTests(
|
|
|
441
467
|
result: DiscoveredServer,
|
|
442
468
|
manifestBody: string,
|
|
443
469
|
): DiscoveredServer {
|
|
444
|
-
return applyManifest(result, {
|
|
470
|
+
return applyManifest(result, {
|
|
471
|
+
statusCode: 200,
|
|
472
|
+
body: manifestBody,
|
|
473
|
+
hostname: new URL(result.bundleUrl).hostname,
|
|
474
|
+
})
|
|
445
475
|
}
|
|
446
476
|
|
|
447
477
|
// ── name/icon enrichment ────────────────────────────────────────────────────
|
|
@@ -490,30 +520,42 @@ function enrichWithManifest(
|
|
|
490
520
|
|
|
491
521
|
if (!enrichmentInFlight.has(result.port)) {
|
|
492
522
|
enrichmentInFlight.add(result.port)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
enrichmentInFlight.delete(port),
|
|
523
|
+
void fetchManifestEnrichment(result, buildIconProxyUrl).finally(() =>
|
|
524
|
+
enrichmentInFlight.delete(result.port),
|
|
496
525
|
)
|
|
497
526
|
}
|
|
498
527
|
return result
|
|
499
528
|
}
|
|
500
529
|
|
|
501
530
|
async function fetchManifestEnrichment(
|
|
502
|
-
|
|
531
|
+
result: DiscoveredServer,
|
|
503
532
|
buildIconProxyUrl?: (externalUrl: string) => string,
|
|
504
533
|
): Promise<void> {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
534
|
+
const hostname = normalizeHostname(new URL(result.bundleUrl).hostname)
|
|
535
|
+
const manifestRes = await httpGet(
|
|
536
|
+
hostname,
|
|
537
|
+
result.port,
|
|
538
|
+
'/',
|
|
539
|
+
'GET',
|
|
540
|
+
ENRICH_TIMEOUT_MS,
|
|
541
|
+
{
|
|
542
|
+
'expo-platform': 'ios',
|
|
543
|
+
},
|
|
544
|
+
)
|
|
508
545
|
if (!manifestRes || manifestRes.statusCode !== 200) return
|
|
509
546
|
// reuse applyManifest's exact field extraction against a throwaway result.
|
|
510
547
|
const parsed = applyManifest(
|
|
511
|
-
{
|
|
548
|
+
{
|
|
549
|
+
port: result.port,
|
|
550
|
+
framework: 'one',
|
|
551
|
+
bundleUrl: `${buildLoopbackProbeBaseUrl('http:', hostname, result.port)}/index.bundle`,
|
|
552
|
+
lastSeen: 0,
|
|
553
|
+
},
|
|
512
554
|
manifestRes,
|
|
513
555
|
buildIconProxyUrl,
|
|
514
556
|
)
|
|
515
557
|
if (!parsed.projectName && !parsed.iconUrl) return
|
|
516
|
-
enrichmentCache.set(port, {
|
|
558
|
+
enrichmentCache.set(result.port, {
|
|
517
559
|
projectName: parsed.projectName,
|
|
518
560
|
iconUrl: parsed.iconUrl,
|
|
519
561
|
iconPath: parsed.iconPath,
|
|
@@ -521,7 +563,7 @@ async function fetchManifestEnrichment(
|
|
|
521
563
|
})
|
|
522
564
|
// the nameless result cached as a strong entry and won't re-probe on its own;
|
|
523
565
|
// drop it so the next scan re-runs probePort and publishes the enriched name.
|
|
524
|
-
portCache.delete(port)
|
|
566
|
+
portCache.delete(result.port)
|
|
525
567
|
}
|
|
526
568
|
|
|
527
569
|
// ports confirmed to be running a non-sootsim dev server — safe to skip the
|
|
@@ -569,7 +611,8 @@ export async function probePort(
|
|
|
569
611
|
// too, or a cold CLI process can build the known app's bundle url before its
|
|
570
612
|
// runtime config is available.
|
|
571
613
|
await loadOptionalDemoApps()
|
|
572
|
-
|
|
614
|
+
const probeHostname = await tcpPing(port)
|
|
615
|
+
if (!probeHostname) return null
|
|
573
616
|
|
|
574
617
|
const suppliedPid = listeningProcess?.pid
|
|
575
618
|
const ownerPid =
|
|
@@ -584,12 +627,18 @@ export async function probePort(
|
|
|
584
627
|
|
|
585
628
|
// wave 1 — safe parallel probes. none of these mutate metro state.
|
|
586
629
|
const [sootsimRes, statusRes, manifestRes, expoRes] = await Promise.all([
|
|
587
|
-
knownNonPatched.has(port)
|
|
588
|
-
|
|
630
|
+
knownNonPatched.has(port)
|
|
631
|
+
? Promise.resolve(null)
|
|
632
|
+
: httpGet(probeHostname, port, '/__soot/'),
|
|
633
|
+
httpGet(probeHostname, port, '/status'),
|
|
589
634
|
knownOne.has(port)
|
|
590
635
|
? Promise.resolve(null)
|
|
591
|
-
: httpGet(port, '/', 'GET', MANIFEST_TIMEOUT_MS, {
|
|
592
|
-
|
|
636
|
+
: httpGet(probeHostname, port, '/', 'GET', MANIFEST_TIMEOUT_MS, {
|
|
637
|
+
'expo-platform': 'ios',
|
|
638
|
+
}),
|
|
639
|
+
knownNonExpo.has(port)
|
|
640
|
+
? Promise.resolve(null)
|
|
641
|
+
: httpGet(probeHostname, port, '/_expo/status'),
|
|
593
642
|
])
|
|
594
643
|
|
|
595
644
|
// remember negative /_expo/status responses so we don't keep probing this
|
|
@@ -627,7 +676,7 @@ export async function probePort(
|
|
|
627
676
|
knownNonPatched.add(port)
|
|
628
677
|
const launchUrl =
|
|
629
678
|
manifestLaunchUrl ||
|
|
630
|
-
|
|
679
|
+
`${buildLoopbackProbeBaseUrl('http:', probeHostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`
|
|
631
680
|
// call the framework "one" only when launchAsset clearly points at
|
|
632
681
|
// a One metro entry; otherwise treat it as plain expo so downstream
|
|
633
682
|
// bundle handling uses the right path semantics.
|
|
@@ -641,7 +690,7 @@ export async function probePort(
|
|
|
641
690
|
port,
|
|
642
691
|
framework,
|
|
643
692
|
bundleUrl: withRuntimeConfig(port, launchUrl, owner.cwd),
|
|
644
|
-
hmrUrl:
|
|
693
|
+
hmrUrl: `${buildLoopbackProbeBaseUrl('ws:', probeHostname, port)}/hot`,
|
|
645
694
|
lastSeen: Date.now(),
|
|
646
695
|
...owner,
|
|
647
696
|
},
|
|
@@ -661,7 +710,12 @@ export async function probePort(
|
|
|
661
710
|
knownNonPatched.add(port)
|
|
662
711
|
return enrichWithManifest(
|
|
663
712
|
applyManifest(
|
|
664
|
-
makeResult(
|
|
713
|
+
makeResult(
|
|
714
|
+
probeHostname,
|
|
715
|
+
port,
|
|
716
|
+
expoRes && expoRes.statusCode === 200 ? 'expo' : 'metro',
|
|
717
|
+
owner,
|
|
718
|
+
),
|
|
665
719
|
manifestRes,
|
|
666
720
|
buildIconProxyUrl,
|
|
667
721
|
),
|
|
@@ -684,10 +738,10 @@ export async function probePort(
|
|
|
684
738
|
framework: 'one',
|
|
685
739
|
bundleUrl: withRuntimeConfig(
|
|
686
740
|
port,
|
|
687
|
-
|
|
741
|
+
`${buildLoopbackProbeBaseUrl('http:', probeHostname, port)}/__soot/bundle.js`,
|
|
688
742
|
owner.cwd,
|
|
689
743
|
),
|
|
690
|
-
hmrUrl:
|
|
744
|
+
hmrUrl: `${buildLoopbackProbeBaseUrl('ws:', probeHostname, port)}/hot`,
|
|
691
745
|
lastSeen: Date.now(),
|
|
692
746
|
patched: true,
|
|
693
747
|
...owner,
|
|
@@ -726,7 +780,7 @@ export async function probePort(
|
|
|
726
780
|
return null
|
|
727
781
|
}
|
|
728
782
|
|
|
729
|
-
const oneRes = await httpGet(port, onePath, 'HEAD')
|
|
783
|
+
const oneRes = await httpGet(probeHostname, port, onePath, 'HEAD')
|
|
730
784
|
if (
|
|
731
785
|
oneRes &&
|
|
732
786
|
oneRes.statusCode > 0 &&
|
|
@@ -742,10 +796,10 @@ export async function probePort(
|
|
|
742
796
|
framework: 'one',
|
|
743
797
|
bundleUrl: withRuntimeConfig(
|
|
744
798
|
port,
|
|
745
|
-
|
|
799
|
+
`${buildLoopbackProbeBaseUrl('http:', probeHostname, port)}${onePath}&minify=false`,
|
|
746
800
|
owner.cwd,
|
|
747
801
|
),
|
|
748
|
-
hmrUrl:
|
|
802
|
+
hmrUrl: `${buildLoopbackProbeBaseUrl('ws:', probeHostname, port)}/hot`,
|
|
749
803
|
lastSeen: Date.now(),
|
|
750
804
|
...owner,
|
|
751
805
|
},
|
package/src/backend-origin.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
const DEFAULT_CONTRAST_BACKEND_ORIGIN = 'https://contrast.dev'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export const LOOPBACK_PROBE_HOSTNAMES = ['::1', '127.0.0.1'] as const
|
|
4
|
+
|
|
5
|
+
export function normalizeHostname(hostname: string): string {
|
|
4
6
|
return hostname.replace(/^\[|\]$/g, '').toLowerCase()
|
|
5
7
|
}
|
|
6
8
|
|
|
9
|
+
export function buildLoopbackProbeBaseUrl(
|
|
10
|
+
protocol: 'http:' | 'ws:',
|
|
11
|
+
hostname: string,
|
|
12
|
+
port: number,
|
|
13
|
+
): string {
|
|
14
|
+
const normalizedHostname = normalizeHostname(hostname)
|
|
15
|
+
const urlHost = normalizedHostname.includes(':')
|
|
16
|
+
? `[${normalizedHostname}]`
|
|
17
|
+
: normalizedHostname
|
|
18
|
+
return `${protocol}//${urlHost}:${port}`
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export function isLoopbackHost(hostname: string): boolean {
|
|
8
22
|
const normalized = normalizeHostname(hostname)
|
|
9
23
|
return (
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildLoopbackProbeBaseUrl,
|
|
3
|
+
isLoopbackHost,
|
|
4
|
+
LOOPBACK_PROBE_HOSTNAMES,
|
|
5
|
+
} from './backend-origin'
|
|
2
6
|
import {
|
|
3
7
|
alignLoopbackBundleUrlWithProbe,
|
|
4
8
|
METRO_BUNDLE_PATH,
|
|
@@ -127,21 +131,12 @@ async function resolvePortViaServerScan(port: number): Promise<ResolvedDevBundle
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
async function
|
|
134
|
+
async function probeBaseUrlBundleDirect(
|
|
131
135
|
baseUrl: string,
|
|
132
136
|
port: number,
|
|
133
137
|
): Promise<ResolvedDevBundle | null> {
|
|
134
138
|
const normalizedBaseUrl = baseUrl.replace(/\/+$/, '')
|
|
135
139
|
|
|
136
|
-
// browser/worker loopback targets resolve through the server-side scanner so
|
|
137
|
-
// they match the dev-server list exactly — see resolvePortViaServerScan.
|
|
138
|
-
try {
|
|
139
|
-
if (isLoopbackHost(new URL(normalizedBaseUrl).hostname)) {
|
|
140
|
-
const viaScan = await resolvePortViaServerScan(port)
|
|
141
|
-
if (viaScan) return viaScan
|
|
142
|
-
}
|
|
143
|
-
} catch {}
|
|
144
|
-
|
|
145
140
|
try {
|
|
146
141
|
const manifestRes = await fetchDevProbe(`${normalizedBaseUrl}/`, {
|
|
147
142
|
headers: { 'expo-platform': 'ios' },
|
|
@@ -198,8 +193,30 @@ async function probeBaseUrlBundle(
|
|
|
198
193
|
return null
|
|
199
194
|
}
|
|
200
195
|
|
|
196
|
+
async function probeBaseUrlBundle(
|
|
197
|
+
baseUrl: string,
|
|
198
|
+
port: number,
|
|
199
|
+
): Promise<ResolvedDevBundle | null> {
|
|
200
|
+
// browser/worker loopback targets resolve through the server-side scanner so
|
|
201
|
+
// they match the dev-server list exactly — see resolvePortViaServerScan.
|
|
202
|
+
try {
|
|
203
|
+
if (isLoopbackHost(new URL(baseUrl).hostname)) {
|
|
204
|
+
const viaScan = await resolvePortViaServerScan(port)
|
|
205
|
+
if (viaScan) return viaScan
|
|
206
|
+
}
|
|
207
|
+
} catch {}
|
|
208
|
+
return probeBaseUrlBundleDirect(baseUrl, port)
|
|
209
|
+
}
|
|
210
|
+
|
|
201
211
|
export async function probePortBundle(port: number): Promise<ResolvedDevBundle | null> {
|
|
202
|
-
|
|
212
|
+
const viaScan = await resolvePortViaServerScan(port)
|
|
213
|
+
if (viaScan) return viaScan
|
|
214
|
+
const candidates = await Promise.all(
|
|
215
|
+
LOOPBACK_PROBE_HOSTNAMES.map((hostname) =>
|
|
216
|
+
probeBaseUrlBundleDirect(buildLoopbackProbeBaseUrl('http:', hostname, port), port),
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
return candidates.find((candidate) => candidate !== null) ?? null
|
|
203
220
|
}
|
|
204
221
|
|
|
205
222
|
export async function resolveConnectionInput(input: string): Promise<ResolvedDevBundle> {
|