rnxsim 0.1.344 → 0.1.346

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.
Files changed (211) hide show
  1. package/README.md +59 -31
  2. package/cli/app-fonts.ts +8 -28
  3. package/cli/app-project.ts +12 -184
  4. package/cli/app-splash.ts +3 -17
  5. package/cli/bin.ts +15 -28
  6. package/cli/commands/control.ts +20 -30
  7. package/cli/commands/daemon.ts +19 -44
  8. package/cli/commands/flow.ts +0 -27
  9. package/cli/commands/install-desktop.ts +2 -16
  10. package/cli/commands/login.ts +3 -43
  11. package/cli/commands/logout.ts +0 -7
  12. package/cli/commands/serve.ts +2 -32
  13. package/cli/commands/setup.ts +20 -194
  14. package/cli/commands/telemetry.ts +7 -5
  15. package/cli/commands/upload.ts +1 -48
  16. package/cli/commands/welcome.ts +64 -0
  17. package/cli/outbound-endpoints.ts +586 -0
  18. package/cli/privacy.ts +53 -0
  19. package/cli/runtime-notes.ts +2 -2
  20. package/cli/telemetry.ts +46 -149
  21. package/dist-cli/bin.js +6 -6
  22. package/dist-cli/chunks/{agent-HTSE5FA4.js → agent-U6HZUHAQ.js} +2 -2
  23. package/dist-cli/chunks/{agent-wrapper-L6X2AEZL.js → agent-wrapper-IM2VL6XP.js} +2 -2
  24. package/dist-cli/chunks/{app-fonts-FGF3ETYH.js → app-fonts-LHVTPWCN.js} +2 -2
  25. package/dist-cli/chunks/{assert-HA2ZYVDN.js → assert-UMGCZXWN.js} +2 -2
  26. package/dist-cli/chunks/{auth-HONDAV75.js → auth-PQY57FPY.js} +2 -2
  27. package/dist-cli/chunks/{beta-U7EE4GMR.js → beta-3MMNRHL7.js} +2 -2
  28. package/dist-cli/chunks/{camera-RTKNUTIX.js → camera-UKISEHMH.js} +2 -2
  29. package/dist-cli/chunks/{chunk-CNLPLCO2.js → chunk-36P343FE.js} +2 -2
  30. package/dist-cli/chunks/{chunk-IWR4HFC4.js → chunk-3KPJVR63.js} +2 -2
  31. package/dist-cli/chunks/chunk-54Y5IXWQ.js +15 -0
  32. package/dist-cli/chunks/{chunk-EPEZZ6YS.js → chunk-5KMV7NAJ.js} +2 -2
  33. package/dist-cli/chunks/{chunk-NDGAF5OF.js → chunk-5WIYZI5H.js} +2 -2
  34. package/dist-cli/chunks/{chunk-KBSVSAAE.js → chunk-6GXUQ3DM.js} +2 -2
  35. package/dist-cli/chunks/{chunk-KT6WMCUS.js → chunk-6MUEKKJ4.js} +1 -1
  36. package/dist-cli/chunks/{chunk-TOHQI2II.js → chunk-6Q6S53R7.js} +1 -1
  37. package/dist-cli/chunks/{chunk-63NZGJ4A.js → chunk-6SQCDLTJ.js} +1 -1
  38. package/dist-cli/chunks/{chunk-Q2B6EQPB.js → chunk-6UW6O575.js} +2 -2
  39. package/dist-cli/chunks/chunk-6VH3UUYQ.js +4 -0
  40. package/dist-cli/chunks/{chunk-BGLWR43R.js → chunk-6XJGGFMD.js} +1 -1
  41. package/dist-cli/chunks/{chunk-TZNRKIS3.js → chunk-75LMZXF5.js} +2 -2
  42. package/dist-cli/chunks/{chunk-FZ5DRZJE.js → chunk-7CJNUNQA.js} +3 -3
  43. package/dist-cli/chunks/{chunk-UCVQOQCY.js → chunk-BQDGFR4B.js} +2 -2
  44. package/dist-cli/chunks/{chunk-H4J7EWZC.js → chunk-BSIVS2SQ.js} +1 -1
  45. package/dist-cli/chunks/{chunk-6E6SBYIO.js → chunk-CD2LITQX.js} +1 -1
  46. package/dist-cli/chunks/{chunk-PNMCALX5.js → chunk-DGH3D6WS.js} +2 -2
  47. package/dist-cli/chunks/chunk-EP6XBKPM.js +11 -0
  48. package/dist-cli/chunks/{chunk-OCUXAA6O.js → chunk-EVX6Y7ZM.js} +2 -2
  49. package/dist-cli/chunks/chunk-EYRKIWHE.js +4 -0
  50. package/dist-cli/chunks/{chunk-N5PFDTFL.js → chunk-FSYB2S6Q.js} +17 -17
  51. package/dist-cli/chunks/{chunk-6PEZSVIG.js → chunk-HPCTBEAP.js} +1 -1
  52. package/dist-cli/chunks/chunk-HS2BME6V.js +6 -0
  53. package/dist-cli/chunks/{chunk-G6IPT2PS.js → chunk-I3GUDHG2.js} +2 -2
  54. package/dist-cli/chunks/{chunk-MWUXQIA3.js → chunk-I4HVFKLM.js} +2 -2
  55. package/dist-cli/chunks/{chunk-ZPTFL5FQ.js → chunk-IBNK7SWL.js} +2 -2
  56. package/dist-cli/chunks/chunk-IUG4GWQC.js +25 -0
  57. package/dist-cli/chunks/{chunk-XQFDEY3F.js → chunk-IUIKD2YI.js} +1 -1
  58. package/dist-cli/chunks/{chunk-JFUVWIJT.js → chunk-JPVXLY7X.js} +1 -1
  59. package/dist-cli/chunks/{chunk-JLUEQPEF.js → chunk-K4PZLT2K.js} +1 -1
  60. package/dist-cli/chunks/{chunk-CRWYKW67.js → chunk-KNIKUJPD.js} +1 -1
  61. package/dist-cli/chunks/{chunk-EZLLZDYX.js → chunk-L6GWDWGZ.js} +2 -2
  62. package/dist-cli/chunks/chunk-LAVDDUQR.js +4 -0
  63. package/dist-cli/chunks/{chunk-22WJPMTQ.js → chunk-LPXKPZ4B.js} +1 -1
  64. package/dist-cli/chunks/{chunk-IPC45HTT.js → chunk-LYLK5VRP.js} +2 -2
  65. package/dist-cli/chunks/{chunk-S6LBAUWH.js → chunk-ME46FC4L.js} +1 -1
  66. package/dist-cli/chunks/chunk-NNXSLD76.js +99 -0
  67. package/dist-cli/chunks/{chunk-7V4ULF3U.js → chunk-PNH3YBZ4.js} +2 -2
  68. package/dist-cli/chunks/{chunk-GRX6NSBG.js → chunk-PPEJMNRO.js} +2 -2
  69. package/dist-cli/chunks/{chunk-K3VNCCEJ.js → chunk-SEWGXJG6.js} +3 -3
  70. package/dist-cli/chunks/{chunk-R3UYHE6G.js → chunk-T4JPZI32.js} +1 -1
  71. package/dist-cli/chunks/{chunk-NRTHFEPM.js → chunk-TAQYETRH.js} +2 -2
  72. package/dist-cli/chunks/{chunk-7JEEO33Y.js → chunk-TBVBWJJL.js} +2 -2
  73. package/dist-cli/chunks/{chunk-HRBMD2FS.js → chunk-UA54YLWR.js} +26 -26
  74. package/dist-cli/chunks/{chunk-G3UAFZE2.js → chunk-UV3TXNMG.js} +1 -1
  75. package/dist-cli/chunks/{chunk-EH35QQG4.js → chunk-VZ6YFPIN.js} +45 -45
  76. package/dist-cli/chunks/chunk-W54SRZ5K.js +14 -0
  77. package/dist-cli/chunks/chunk-WAC75WN6.js +4 -0
  78. package/dist-cli/chunks/{chunk-S7CSBQ6G.js → chunk-X2WIEZ7Q.js} +1 -1
  79. package/dist-cli/chunks/{chunk-EN32LN2B.js → chunk-XWTYFWZ3.js} +2 -2
  80. package/dist-cli/chunks/chunk-Y2C3CZVD.js +4 -0
  81. package/dist-cli/chunks/chunk-YM4L2CFB.js +4 -0
  82. package/dist-cli/chunks/{chunk-ZZSBCGHC.js → chunk-YXDR4CAQ.js} +1 -1
  83. package/dist-cli/chunks/{chunk-6NLWKL7Y.js → chunk-Z6P427WC.js} +2 -2
  84. package/dist-cli/chunks/{cleanup-EMYD5WZG.js → cleanup-KXPS5VSK.js} +2 -2
  85. package/dist-cli/chunks/cli-version-LJ6NFLBP.js +4 -0
  86. package/dist-cli/chunks/{compat-W2N5QTVG.js → compat-JWF7N3VO.js} +2 -2
  87. package/dist-cli/chunks/config-M42VJXNK.js +13 -0
  88. package/dist-cli/chunks/{control-4SFBROUE.js → control-HMZDIS6P.js} +2 -2
  89. package/dist-cli/chunks/daemon-MQ4UYKNQ.js +4 -0
  90. package/dist-cli/chunks/{debug-HCR2KFUA.js → debug-F44GWYMF.js} +3 -3
  91. package/dist-cli/chunks/{desktop-T2KS5PBJ.js → desktop-YJMWW6MW.js} +3 -3
  92. package/dist-cli/chunks/{detox-5S7EHINH.js → detox-5B4ASTJO.js} +2 -2
  93. package/dist-cli/chunks/{device-LVM3MZRJ.js → device-2544YZNY.js} +2 -2
  94. package/dist-cli/chunks/{diagnose-WK37XKLI.js → diagnose-LS4245JR.js} +2 -2
  95. package/dist-cli/chunks/{disk-cleanup-X7VJOXMS.js → disk-cleanup-XYL2HQQY.js} +2 -2
  96. package/dist-cli/chunks/drivers-P5CRSKH6.js +4 -0
  97. package/dist-cli/chunks/{film-7KLTI3ER.js → film-LS3TIZRM.js} +3 -3
  98. package/dist-cli/chunks/flow-7V77GXAB.js +4 -0
  99. package/dist-cli/chunks/help-HRESIFZX.js +4 -0
  100. package/dist-cli/chunks/{hidden-runtime-alias-4RVGUVFO.js → hidden-runtime-alias-HGN2EEQR.js} +2 -2
  101. package/dist-cli/chunks/{home-paths-5DHSAEGZ.js → home-paths-T3NTXE4U.js} +2 -2
  102. package/dist-cli/chunks/inspect-UC44UH4M.js +4 -0
  103. package/dist-cli/chunks/install-desktop-RDV66JHG.js +4 -0
  104. package/dist-cli/chunks/login-WVP5H5NQ.js +26 -0
  105. package/dist-cli/chunks/logout-GXX5OY6X.js +4 -0
  106. package/dist-cli/chunks/{maestro-HTRLQDJE.js → maestro-MQMGBNOB.js} +3 -3
  107. package/dist-cli/chunks/{maestro-generate-GX2QNA3F.js → maestro-generate-CO355QV2.js} +3 -3
  108. package/dist-cli/chunks/{mode-GM6KZHQP.js → mode-QFB3M2AA.js} +2 -2
  109. package/dist-cli/chunks/{optional-demo-registry-B4TD7JQP.js → optional-demo-registry-UIPVSBHV.js} +2 -2
  110. package/dist-cli/chunks/{perf-3PRXJQIC.js → perf-TDOSW3LD.js} +2 -2
  111. package/dist-cli/chunks/{permissions-TJZIRF2S.js → permissions-TEVQLCGJ.js} +2 -2
  112. package/dist-cli/chunks/privacy-DVHAMHR6.js +4 -0
  113. package/dist-cli/chunks/{record-LI55DTPE.js → record-OFEQLSWA.js} +3 -3
  114. package/dist-cli/chunks/{report-issue-TBGJVQIQ.js → report-issue-B2UPOERJ.js} +2 -2
  115. package/dist-cli/chunks/reset-XQQYM55B.js +4 -0
  116. package/dist-cli/chunks/runtime-GN6HITVB.js +4 -0
  117. package/dist-cli/chunks/{screenshot-command-BEN7YYIF.js → screenshot-command-NTLAY7QN.js} +3 -3
  118. package/dist-cli/chunks/{screenshot-layers-3IU4KYNO.js → screenshot-layers-BBLKNRQU.js} +2 -2
  119. package/dist-cli/chunks/{screenshots-capture-ZRC54ST6.js → screenshots-capture-6GHIRCTG.js} +2 -2
  120. package/dist-cli/chunks/serve-H6GJ4OHW.js +118 -0
  121. package/dist-cli/chunks/setup-6YLKSMWS.js +30 -0
  122. package/dist-cli/chunks/{skills-GKBR2HQB.js → skills-FUL7D6C7.js} +2 -2
  123. package/dist-cli/chunks/{state-LIHZJSL7.js → state-O2HE7XSR.js} +2 -2
  124. package/dist-cli/chunks/{storage-4TPN3NFB.js → storage-WIZTF7RN.js} +4 -4
  125. package/dist-cli/chunks/store-NJ2MBZC2.js +4 -0
  126. package/dist-cli/chunks/telemetry-BEC6W5WP.js +4 -0
  127. package/dist-cli/chunks/{timeline-EX63UNWU.js → timeline-MA4JT7UJ.js} +2 -2
  128. package/dist-cli/chunks/{upgrade-O6Y6EINE.js → upgrade-H5LSDXBH.js} +2 -2
  129. package/dist-cli/chunks/upload-Q4DWKIA4.js +4 -0
  130. package/dist-cli/chunks/{version-HLNL3NFB.js → version-4UWEEDVP.js} +2 -2
  131. package/dist-cli/chunks/{web-4EDPYPW7.js → web-F3LXRN3K.js} +2 -2
  132. package/dist-cli/chunks/welcome-AZBOSIG2.js +5 -0
  133. package/dist-cli/chunks/{what-happened-77VNUEPK.js → what-happened-6OSNLOAY.js} +2 -2
  134. package/dist-lib/agent-daemon-client.cjs +1 -1
  135. package/dist-lib/agent-events.cjs +1 -1
  136. package/dist-lib/agent-identity.cjs +1 -1
  137. package/dist-lib/agent-sessions.cjs +1 -1
  138. package/dist-lib/attached-projects.cjs +1 -1
  139. package/dist-lib/auth/shared-session.cjs +1 -1
  140. package/dist-lib/backend-origin.cjs +13 -1
  141. package/dist-lib/beta.cjs +1 -1
  142. package/dist-lib/beta.mjs +1 -1
  143. package/dist-lib/bridge-constants.cjs +1 -1
  144. package/dist-lib/bridge-contract.cjs +1 -1
  145. package/dist-lib/cli-constants.cjs +1 -1
  146. package/dist-lib/config.cjs +1 -1
  147. package/dist-lib/detox/index.cjs +1 -1
  148. package/dist-lib/dev-bundle-resolution.cjs +43 -13
  149. package/dist-lib/home-paths.cjs +145 -11
  150. package/dist-lib/host/bridge-host.cjs +326 -78
  151. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  152. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  153. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  154. package/dist-lib/host/replacement-module-handler.cjs +71 -4
  155. package/dist-lib/host/websocket-proxy.cjs +1 -1
  156. package/dist-lib/index.cjs +337 -37
  157. package/dist-lib/jump-to-source-babel.cjs +1 -1
  158. package/dist-lib/menu.cjs +1 -1
  159. package/dist-lib/menu.mjs +1 -1
  160. package/dist-lib/metro-production-bundle.cjs +1 -1
  161. package/dist-lib/metro-production-bundle.mjs +1 -1
  162. package/dist-lib/metro.cjs +484 -14
  163. package/dist-lib/profiles.cjs +1 -1
  164. package/dist-lib/public-brand.cjs +1 -1
  165. package/dist-lib/react-native-host-modules.cjs +1 -1
  166. package/dist-lib/react-native-host-modules.mjs +1 -1
  167. package/dist-lib/render-mode.cjs +1 -1
  168. package/dist-lib/scripts/dev-server-scanner.cjs +120 -50
  169. package/dist-lib/sdk.cjs +1 -1
  170. package/dist-lib/sdk.mjs +1 -1
  171. package/dist-lib/skills.cjs +493 -439
  172. package/dist-lib/vite.cjs +193 -37
  173. package/package.json +2 -1
  174. package/scripts/dev-server-scanner.ts +112 -51
  175. package/skills/rnx-test/SKILL.md +4 -0
  176. package/src/backend-origin.ts +15 -1
  177. package/src/dev-bundle-resolution.ts +42 -18
  178. package/src/dev-server-open.ts +197 -0
  179. package/src/home-paths.ts +168 -15
  180. package/src/host/bridge-host.ts +2 -2
  181. package/src/host/replacement-module-handler.ts +68 -2
  182. package/src/metro-plugin.ts +83 -5
  183. package/src/native-dev-bundle-url.ts +22 -0
  184. package/src/vite-plugin-one.ts +42 -28
  185. package/dist-cli/chunks/chunk-3JC7H4PR.js +0 -25
  186. package/dist-cli/chunks/chunk-6DLIC7BZ.js +0 -4
  187. package/dist-cli/chunks/chunk-7TNPO2OJ.js +0 -9
  188. package/dist-cli/chunks/chunk-AMF5KTOI.js +0 -4
  189. package/dist-cli/chunks/chunk-BX5VXTOQ.js +0 -101
  190. package/dist-cli/chunks/chunk-D73H7ACZ.js +0 -4
  191. package/dist-cli/chunks/chunk-DY4O7KYI.js +0 -4
  192. package/dist-cli/chunks/chunk-GU2V4JDG.js +0 -15
  193. package/dist-cli/chunks/chunk-POOCKX5B.js +0 -14
  194. package/dist-cli/chunks/chunk-Z2HMSVSN.js +0 -4
  195. package/dist-cli/chunks/cli-version-CN3NTKLH.js +0 -4
  196. package/dist-cli/chunks/config-SELKGT4F.js +0 -13
  197. package/dist-cli/chunks/daemon-V2SFCU2E.js +0 -4
  198. package/dist-cli/chunks/drivers-3YFTJ5WA.js +0 -4
  199. package/dist-cli/chunks/flow-36ANTMJU.js +0 -4
  200. package/dist-cli/chunks/help-ZGL26SPK.js +0 -4
  201. package/dist-cli/chunks/inspect-R3B55RTS.js +0 -4
  202. package/dist-cli/chunks/install-desktop-TZ3H3VWK.js +0 -4
  203. package/dist-cli/chunks/login-4DFULI7S.js +0 -28
  204. package/dist-cli/chunks/logout-QMCVYMBB.js +0 -4
  205. package/dist-cli/chunks/reset-O6MW6X7X.js +0 -4
  206. package/dist-cli/chunks/runtime-JESXBHRQ.js +0 -4
  207. package/dist-cli/chunks/serve-QGKTWPNF.js +0 -118
  208. package/dist-cli/chunks/setup-3WPWATCX.js +0 -32
  209. package/dist-cli/chunks/store-VBX3L46S.js +0 -4
  210. package/dist-cli/chunks/telemetry-ACNNIQIG.js +0 -4
  211. package/dist-cli/chunks/upload-OD2UQHMM.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.344 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.346 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -116,6 +116,21 @@ var import_net = __toESM(require("net"), 1);
116
116
  var import_node_path3 = require("node:path");
117
117
  var import_util = require("util");
118
118
 
119
+ // src/backend-origin.ts
120
+ var LOOPBACK_PROBE_HOSTNAMES = ["::1", "127.0.0.1"];
121
+ function normalizeHostname(hostname) {
122
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
123
+ }
124
+ function buildLoopbackProbeBaseUrl(protocol, hostname, port) {
125
+ const normalizedHostname = normalizeHostname(hostname);
126
+ const urlHost = normalizedHostname.includes(":") ? `[${normalizedHostname}]` : normalizedHostname;
127
+ return `${protocol}//${urlHost}:${port}`;
128
+ }
129
+ function isLoopbackHost(hostname) {
130
+ const normalized = normalizeHostname(hostname);
131
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
132
+ }
133
+
119
134
  // src/config.ts
120
135
  var SOOTSIM_CONFIG_QUERY_PARAM = "sootsimConfig";
121
136
  function hasOwnKeys(value) {
@@ -142,6 +157,19 @@ function isAbsoluteHttpUrl(url) {
142
157
  function isNativeDevBundlePath(pathname) {
143
158
  return pathname.endsWith(".bundle");
144
159
  }
160
+ function alignLoopbackBundleUrlWithProbe(bundleUrl, probeBaseUrl) {
161
+ try {
162
+ const bundle = new URL(bundleUrl);
163
+ const probe = new URL(probeBaseUrl);
164
+ if (!isLoopbackHost(bundle.hostname) || !isLoopbackHost(probe.hostname)) {
165
+ return bundleUrl;
166
+ }
167
+ bundle.hostname = probe.hostname;
168
+ return bundle.toString();
169
+ } catch {
170
+ return bundleUrl;
171
+ }
172
+ }
145
173
  function applyPreviewProdBundle(parsed) {
146
174
  if (!process.env.SOOTSIM_PREVIEW_PROD_BUNDLE) return;
147
175
  parsed.searchParams.set("dev", "false");
@@ -514,27 +542,36 @@ var execP = (0, import_util.promisify)(import_child_process.exec);
514
542
  var TIMEOUT_MS = 250;
515
543
  var MANIFEST_TIMEOUT_MS = 1500;
516
544
  var TCP_GATE_MS = 120;
517
- function tcpPing(port, timeout = TCP_GATE_MS) {
518
- return new Promise((resolve2) => {
519
- const sock = new import_net.default.Socket();
520
- let settled = false;
521
- const done = (ok) => {
522
- if (settled) return;
523
- settled = true;
524
- sock.destroy();
525
- resolve2(ok);
526
- };
527
- sock.setTimeout(timeout);
528
- sock.once("connect", () => done(true));
529
- sock.once("timeout", () => done(false));
530
- sock.once("error", () => done(false));
531
- sock.connect(port, "localhost");
532
- });
545
+ async function tcpPing(port, timeout = TCP_GATE_MS) {
546
+ try {
547
+ return await Promise.any(
548
+ LOOPBACK_PROBE_HOSTNAMES.map(
549
+ (hostname) => new Promise((resolve2, reject) => {
550
+ const sock = new import_net.default.Socket();
551
+ let settled = false;
552
+ const done = (reachable) => {
553
+ if (settled) return;
554
+ settled = true;
555
+ sock.destroy();
556
+ if (reachable) resolve2(hostname);
557
+ else reject(new Error(`unreachable loopback host ${hostname}`));
558
+ };
559
+ sock.setTimeout(timeout);
560
+ sock.once("connect", () => done(true));
561
+ sock.once("timeout", () => done(false));
562
+ sock.once("error", () => done(false));
563
+ sock.connect(port, hostname);
564
+ })
565
+ )
566
+ );
567
+ } catch {
568
+ return null;
569
+ }
533
570
  }
534
- function httpGet(port, path10, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
571
+ function httpGet(hostname, port, path10, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
535
572
  return new Promise((resolve2) => {
536
573
  const req = import_http.default.request(
537
- { hostname: "localhost", port, path: path10, method, timeout, headers },
574
+ { hostname, port, path: path10, method, timeout, headers },
538
575
  (res) => {
539
576
  let body = "";
540
577
  res.on("data", (c) => body += c.toString());
@@ -546,7 +583,12 @@ function httpGet(port, path10, method = "GET", timeout = TIMEOUT_MS, headers = {
546
583
  })();
547
584
  res.on(
548
585
  "end",
549
- () => resolve2({ statusCode: res.statusCode || 0, body, contentType })
586
+ () => resolve2({
587
+ statusCode: res.statusCode || 0,
588
+ body,
589
+ contentType,
590
+ hostname
591
+ })
550
592
  );
551
593
  }
552
594
  );
@@ -698,16 +740,16 @@ async function resolveProcessCwd(pid) {
698
740
  }
699
741
  return null;
700
742
  }
701
- function makeResult(port, framework, owner) {
743
+ function makeResult(hostname, port, framework, owner) {
702
744
  return {
703
745
  port,
704
746
  framework,
705
747
  bundleUrl: withRuntimeConfig(
706
748
  port,
707
- `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,
749
+ `${buildLoopbackProbeBaseUrl("http:", hostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,
708
750
  owner.cwd
709
751
  ),
710
- hmrUrl: `ws://localhost:${port}/hot`,
752
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", hostname, port)}/hot`,
711
753
  lastSeen: Date.now(),
712
754
  ...owner
713
755
  };
@@ -738,7 +780,11 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
738
780
  if (client.name) result.projectName = client.name;
739
781
  if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
740
782
  if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
741
- const launchUrl2 = manifest?.launchAsset?.url;
783
+ const rawLaunchUrl = manifest?.launchAsset?.url;
784
+ const launchUrl2 = typeof rawLaunchUrl === "string" ? alignLoopbackBundleUrlWithProbe(
785
+ rawLaunchUrl,
786
+ buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)
787
+ ) : rawLaunchUrl;
742
788
  if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
743
789
  result.bundleUrl = withRuntimeConfig(result.port, launchUrl2, result.cwd);
744
790
  }
@@ -751,11 +797,11 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
751
797
  } else {
752
798
  const cleanPath = rawIconUrl.replace(/^\.\//, "");
753
799
  result.iconUrl = buildIconProxyUrl(
754
- `http://localhost:${result.port}/assets/${cleanPath}`
800
+ `${buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)}/assets/${cleanPath}`
755
801
  );
756
802
  }
757
803
  } else {
758
- result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `http://localhost:${result.port}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
804
+ result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `${buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
759
805
  }
760
806
  }
761
807
  } catch {
@@ -779,38 +825,51 @@ function enrichWithManifest(result, buildIconProxyUrl) {
779
825
  }
780
826
  if (!enrichmentInFlight.has(result.port)) {
781
827
  enrichmentInFlight.add(result.port);
782
- const port = result.port;
783
- void fetchManifestEnrichment(port, buildIconProxyUrl).finally(
784
- () => enrichmentInFlight.delete(port)
828
+ void fetchManifestEnrichment(result, buildIconProxyUrl).finally(
829
+ () => enrichmentInFlight.delete(result.port)
785
830
  );
786
831
  }
787
832
  return result;
788
833
  }
789
- async function fetchManifestEnrichment(port, buildIconProxyUrl) {
790
- const manifestRes = await httpGet(port, "/", "GET", ENRICH_TIMEOUT_MS, {
791
- "expo-platform": "ios"
792
- });
834
+ async function fetchManifestEnrichment(result, buildIconProxyUrl) {
835
+ const hostname = normalizeHostname(new URL(result.bundleUrl).hostname);
836
+ const manifestRes = await httpGet(
837
+ hostname,
838
+ result.port,
839
+ "/",
840
+ "GET",
841
+ ENRICH_TIMEOUT_MS,
842
+ {
843
+ "expo-platform": "ios"
844
+ }
845
+ );
793
846
  if (!manifestRes || manifestRes.statusCode !== 200) return;
794
847
  const parsed = applyManifest(
795
- { port, framework: "one", bundleUrl: "", lastSeen: 0 },
848
+ {
849
+ port: result.port,
850
+ framework: "one",
851
+ bundleUrl: `${buildLoopbackProbeBaseUrl("http:", hostname, result.port)}/index.bundle`,
852
+ lastSeen: 0
853
+ },
796
854
  manifestRes,
797
855
  buildIconProxyUrl
798
856
  );
799
857
  if (!parsed.projectName && !parsed.iconUrl) return;
800
- enrichmentCache.set(port, {
858
+ enrichmentCache.set(result.port, {
801
859
  projectName: parsed.projectName,
802
860
  iconUrl: parsed.iconUrl,
803
861
  iconPath: parsed.iconPath,
804
862
  bundleId: parsed.bundleId
805
863
  });
806
- portCache.delete(port);
864
+ portCache.delete(result.port);
807
865
  }
808
866
  var knownNonPatched = /* @__PURE__ */ new Set();
809
867
  var knownNonExpo = /* @__PURE__ */ new Set();
810
868
  var knownOne = /* @__PURE__ */ new Set();
811
869
  async function probePort(port, buildIconProxyUrl, listeningProcess) {
812
870
  await loadOptionalDemoApps();
813
- if (!await tcpPing(port)) return null;
871
+ const probeHostname = await tcpPing(port);
872
+ if (!probeHostname) return null;
814
873
  const suppliedPid = listeningProcess?.pid;
815
874
  const ownerPid = suppliedPid && suppliedPid > 0 ? suppliedPid : await resolveListeningPid(port);
816
875
  const ownerCwd = ownerPid ? await resolveProcessCwd(ownerPid) : null;
@@ -820,10 +879,12 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
820
879
  };
821
880
  const onePath = `/node_modules/one/metro-entry.bundle?platform=ios&dev=true`;
822
881
  const [sootsimRes, statusRes, manifestRes, expoRes] = await Promise.all([
823
- knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(port, "/__soot/"),
824
- httpGet(port, "/status"),
825
- knownOne.has(port) ? Promise.resolve(null) : httpGet(port, "/", "GET", MANIFEST_TIMEOUT_MS, { "expo-platform": "ios" }),
826
- knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(port, "/_expo/status")
882
+ knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/__soot/"),
883
+ httpGet(probeHostname, port, "/status"),
884
+ knownOne.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/", "GET", MANIFEST_TIMEOUT_MS, {
885
+ "expo-platform": "ios"
886
+ }),
887
+ knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/_expo/status")
827
888
  ]);
828
889
  if (expoRes && expoRes.statusCode === 200) {
829
890
  knownNonExpo.delete(port);
@@ -835,7 +896,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
835
896
  const manifestClient = manifestParsed?.extra?.expoClient || manifestParsed?.extra || {};
836
897
  if (manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string")) {
837
898
  knownNonPatched.add(port);
838
- const launchUrl2 = manifestLaunchUrl || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
899
+ const launchUrl2 = manifestLaunchUrl || `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
839
900
  const framework = launchUrl2.includes(
840
901
  "/one/metro-entry.bundle"
841
902
  ) ? "one" : "expo";
@@ -844,7 +905,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
844
905
  port,
845
906
  framework,
846
907
  bundleUrl: withRuntimeConfig(port, launchUrl2, owner.cwd),
847
- hmrUrl: `ws://localhost:${port}/hot`,
908
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
848
909
  lastSeen: Date.now(),
849
910
  ...owner
850
911
  },
@@ -856,7 +917,12 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
856
917
  knownNonPatched.add(port);
857
918
  return enrichWithManifest(
858
919
  applyManifest(
859
- makeResult(port, expoRes && expoRes.statusCode === 200 ? "expo" : "metro", owner),
920
+ makeResult(
921
+ probeHostname,
922
+ port,
923
+ expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
924
+ owner
925
+ ),
860
926
  manifestRes,
861
927
  buildIconProxyUrl
862
928
  ),
@@ -872,10 +938,10 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
872
938
  framework: "one",
873
939
  bundleUrl: withRuntimeConfig(
874
940
  port,
875
- `http://localhost:${port}/__soot/bundle.js`,
941
+ `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/__soot/bundle.js`,
876
942
  owner.cwd
877
943
  ),
878
- hmrUrl: `ws://localhost:${port}/hot`,
944
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
879
945
  lastSeen: Date.now(),
880
946
  patched: true,
881
947
  ...owner
@@ -890,7 +956,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
890
956
  if (looksLikeMetroPackager) {
891
957
  return null;
892
958
  }
893
- const oneRes = await httpGet(port, onePath, "HEAD");
959
+ const oneRes = await httpGet(probeHostname, port, onePath, "HEAD");
894
960
  if (oneRes && oneRes.statusCode > 0 && oneRes.statusCode < 400 && /application\/javascript/i.test(oneRes.contentType || "")) {
895
961
  knownNonPatched.add(port);
896
962
  knownOne.add(port);
@@ -901,10 +967,10 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
901
967
  framework: "one",
902
968
  bundleUrl: withRuntimeConfig(
903
969
  port,
904
- `http://localhost:${port}${onePath}&minify=false`,
970
+ `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}${onePath}&minify=false`,
905
971
  owner.cwd
906
972
  ),
907
- hmrUrl: `ws://localhost:${port}/hot`,
973
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
908
974
  lastSeen: Date.now(),
909
975
  ...owner
910
976
  },
@@ -1064,6 +1130,7 @@ function getCliVersion() {
1064
1130
  }
1065
1131
 
1066
1132
  // src/home-paths.ts
1133
+ var import_node_crypto = require("node:crypto");
1067
1134
  var import_node_fs3 = __toESM(require("node:fs"), 1);
1068
1135
  var import_node_os = require("node:os");
1069
1136
  var import_node_path4 = __toESM(require("node:path"), 1);
@@ -1197,6 +1264,7 @@ function activeRuntimeDir() {
1197
1264
  return null;
1198
1265
  }
1199
1266
  var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
1267
+ var DAEMON_CLAIM_STALE_MS = 3e4;
1200
1268
  function readDaemonLockfile() {
1201
1269
  try {
1202
1270
  const fd = import_node_fs3.default.openSync(daemonLockfilePath(), "r");
@@ -1239,10 +1307,30 @@ function isDaemonLockfileFresh(lock, now = Date.now()) {
1239
1307
  }
1240
1308
  function writeDaemonLockfile(data) {
1241
1309
  ensureSootsimHome();
1242
- const tmp = `${daemonLockfilePath()}.tmp`;
1310
+ const lockfilePath = daemonLockfilePath();
1311
+ const existing = readDaemonLockfile();
1312
+ if (!existing) {
1313
+ if (createDaemonLockfileExclusive(data)) return;
1314
+ throw new Error("another rnx daemon claimed the lockfile before this write");
1315
+ }
1316
+ if (!sameDaemonOwner(existing, data)) {
1317
+ throw new Error("refusing to overwrite another rnx daemon lockfile");
1318
+ }
1319
+ const tmp = `${lockfilePath}.${data.pid}.${(0, import_node_crypto.randomUUID)()}.tmp`;
1243
1320
  import_node_fs3.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
1244
1321
  `, "utf8");
1245
- import_node_fs3.default.renameSync(tmp, daemonLockfilePath());
1322
+ try {
1323
+ const current = readDaemonLockfile();
1324
+ if (!current || !sameDaemonOwner(current, data)) {
1325
+ throw new Error("rnx daemon lock ownership changed during heartbeat");
1326
+ }
1327
+ import_node_fs3.default.renameSync(tmp, lockfilePath);
1328
+ } finally {
1329
+ try {
1330
+ import_node_fs3.default.unlinkSync(tmp);
1331
+ } catch {
1332
+ }
1333
+ }
1246
1334
  }
1247
1335
  function writeDevBridgeLockfile(data) {
1248
1336
  import_node_fs3.default.mkdirSync(devBridgesDir(), { recursive: true });
@@ -1252,17 +1340,130 @@ function writeDevBridgeLockfile(data) {
1252
1340
  `, "utf8");
1253
1341
  import_node_fs3.default.renameSync(tmp, lockfilePath);
1254
1342
  }
1343
+ function sameDaemonOwner(left, right) {
1344
+ return left.pid === right.pid && left.startedAt === right.startedAt;
1345
+ }
1346
+ function isAlreadyExists(error) {
1347
+ return error instanceof Error && "code" in error && Reflect.get(error, "code") === "EEXIST";
1348
+ }
1349
+ function createDaemonLockfileExclusive(data) {
1350
+ const lockfilePath = daemonLockfilePath();
1351
+ const tmp = `${lockfilePath}.${data.pid}.${(0, import_node_crypto.randomUUID)()}.claim`;
1352
+ import_node_fs3.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
1353
+ `, "utf8");
1354
+ try {
1355
+ import_node_fs3.default.linkSync(tmp, lockfilePath);
1356
+ return true;
1357
+ } catch (error) {
1358
+ if (isAlreadyExists(error)) return false;
1359
+ throw error;
1360
+ } finally {
1361
+ try {
1362
+ import_node_fs3.default.unlinkSync(tmp);
1363
+ } catch {
1364
+ }
1365
+ }
1366
+ }
1367
+ function daemonClaimPath() {
1368
+ return `${daemonLockfilePath()}.claim`;
1369
+ }
1370
+ function parseDaemonClaim(raw) {
1371
+ try {
1372
+ const value = JSON.parse(raw);
1373
+ if (value !== null && typeof value === "object" && typeof Reflect.get(value, "pid") === "number" && typeof Reflect.get(value, "startedAt") === "number" && typeof Reflect.get(value, "createdAt") === "number") {
1374
+ return {
1375
+ pid: Reflect.get(value, "pid"),
1376
+ startedAt: Reflect.get(value, "startedAt"),
1377
+ createdAt: Reflect.get(value, "createdAt")
1378
+ };
1379
+ }
1380
+ } catch {
1381
+ }
1382
+ return null;
1383
+ }
1384
+ function readDaemonClaim() {
1385
+ try {
1386
+ return parseDaemonClaim(import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8"));
1387
+ } catch {
1388
+ return null;
1389
+ }
1390
+ }
1391
+ function isDaemonClaimFresh(claim, now = Date.now()) {
1392
+ if (now - claim.createdAt > DAEMON_CLAIM_STALE_MS) return false;
1393
+ try {
1394
+ process.kill(claim.pid, 0);
1395
+ return true;
1396
+ } catch {
1397
+ return false;
1398
+ }
1399
+ }
1400
+ function acquireDaemonClaim(data) {
1401
+ const claim = {
1402
+ pid: data.pid,
1403
+ startedAt: data.startedAt,
1404
+ createdAt: Date.now()
1405
+ };
1406
+ const serialized = `${JSON.stringify(claim)}
1407
+ `;
1408
+ for (let attempt = 0; attempt < 8; attempt++) {
1409
+ try {
1410
+ import_node_fs3.default.writeFileSync(daemonClaimPath(), serialized, { encoding: "utf8", flag: "wx" });
1411
+ return true;
1412
+ } catch (error) {
1413
+ if (!isAlreadyExists(error)) throw error;
1414
+ }
1415
+ const existing = readDaemonClaim();
1416
+ if (existing && sameDaemonOwner(existing, claim)) return true;
1417
+ if (existing && isDaemonClaimFresh(existing)) return false;
1418
+ let before;
1419
+ try {
1420
+ before = import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8");
1421
+ const latest = parseDaemonClaim(before);
1422
+ if (latest && isDaemonClaimFresh(latest)) return false;
1423
+ if (import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8") !== before) continue;
1424
+ import_node_fs3.default.unlinkSync(daemonClaimPath());
1425
+ } catch {
1426
+ }
1427
+ }
1428
+ return false;
1429
+ }
1430
+ function releaseDaemonClaim(owner) {
1431
+ try {
1432
+ const claim = readDaemonClaim();
1433
+ if (!claim || !sameDaemonOwner(claim, owner)) return;
1434
+ import_node_fs3.default.unlinkSync(daemonClaimPath());
1435
+ } catch {
1436
+ }
1437
+ }
1255
1438
  function claimDaemonLockfile(data) {
1256
1439
  ensureSootsimHome();
1257
- const existing = readDaemonLockfile();
1258
- if (existing && isDaemonLockfileFresh(existing) && existing.pid !== data.pid) {
1259
- return false;
1440
+ if (!acquireDaemonClaim(data)) return false;
1441
+ try {
1442
+ const existing = readDaemonLockfile();
1443
+ if (existing && sameDaemonOwner(existing, data)) {
1444
+ writeDaemonLockfile(data);
1445
+ return true;
1446
+ }
1447
+ if (existing && isDaemonLockfileFresh(existing)) return false;
1448
+ if (existing) {
1449
+ const latest = readDaemonLockfile();
1450
+ if (!latest || !sameDaemonOwner(latest, existing)) return false;
1451
+ if (isDaemonLockfileFresh(latest)) return false;
1452
+ try {
1453
+ import_node_fs3.default.unlinkSync(daemonLockfilePath());
1454
+ } catch {
1455
+ return false;
1456
+ }
1457
+ }
1458
+ return createDaemonLockfileExclusive(data);
1459
+ } finally {
1460
+ releaseDaemonClaim(data);
1260
1461
  }
1261
- writeDaemonLockfile(data);
1262
- return true;
1263
1462
  }
1264
- function removeDaemonLockfile() {
1463
+ function removeDaemonLockfile(owner) {
1265
1464
  try {
1465
+ const lockfile = readDaemonLockfile();
1466
+ if (!lockfile || !sameDaemonOwner(lockfile, owner)) return;
1266
1467
  import_node_fs3.default.unlinkSync(daemonLockfilePath());
1267
1468
  } catch {
1268
1469
  }
@@ -1284,7 +1485,7 @@ var import_url = require("url");
1284
1485
 
1285
1486
  // ../contrast-runtime-delivery/src/index.ts
1286
1487
  var import_node_child_process = require("node:child_process");
1287
- var import_node_crypto = __toESM(require("node:crypto"), 1);
1488
+ var import_node_crypto2 = __toESM(require("node:crypto"), 1);
1288
1489
  var import_node_fs5 = __toESM(require("node:fs"), 1);
1289
1490
  var import_node_os2 = __toESM(require("node:os"), 1);
1290
1491
  var import_node_path6 = __toESM(require("node:path"), 1);
@@ -1664,7 +1865,7 @@ async function downloadToFile(url, destPath) {
1664
1865
  }
1665
1866
  function sha256File(filePath) {
1666
1867
  return new Promise((resolve2, reject) => {
1667
- const hash = import_node_crypto.default.createHash("sha256");
1868
+ const hash = import_node_crypto2.default.createHash("sha256");
1668
1869
  const stream = import_node_fs5.default.createReadStream(filePath);
1669
1870
  stream.on("data", (chunk) => hash.update(chunk));
1670
1871
  stream.on("error", reject);
@@ -1780,7 +1981,7 @@ var import_node_path9 = __toESM(require("node:path"), 1);
1780
1981
 
1781
1982
  // src/agent-sessions.ts
1782
1983
  var import_node_child_process2 = require("node:child_process");
1783
- var import_node_crypto3 = require("node:crypto");
1984
+ var import_node_crypto4 = require("node:crypto");
1784
1985
  var import_node_fs7 = __toESM(require("node:fs"), 1);
1785
1986
  var import_node_path8 = __toESM(require("node:path"), 1);
1786
1987
  var import_node_readline = __toESM(require("node:readline"), 1);
@@ -1825,7 +2026,7 @@ function encodeAgentPromptEnvelope(input) {
1825
2026
  }
1826
2027
 
1827
2028
  // src/attached-projects.ts
1828
- var import_node_crypto2 = require("node:crypto");
2029
+ var import_node_crypto3 = require("node:crypto");
1829
2030
  var import_node_fs6 = __toESM(require("node:fs"), 1);
1830
2031
  var import_node_path7 = __toESM(require("node:path"), 1);
1831
2032
  var overrideDir = null;
@@ -1899,10 +2100,10 @@ function mutateStore(fn) {
1899
2100
  return store;
1900
2101
  }
1901
2102
  function projectIdForCwd(cwd) {
1902
- return (0, import_node_crypto2.createHash)("sha256").update(import_node_path7.default.resolve(cwd)).digest("hex").slice(0, 16);
2103
+ return (0, import_node_crypto3.createHash)("sha256").update(import_node_path7.default.resolve(cwd)).digest("hex").slice(0, 16);
1903
2104
  }
1904
2105
  function newSessionId() {
1905
- return `s_${(0, import_node_crypto2.randomBytes)(10).toString("hex")}`;
2106
+ return `s_${(0, import_node_crypto3.randomBytes)(10).toString("hex")}`;
1906
2107
  }
1907
2108
  function upsertProject(input) {
1908
2109
  const cwd = import_node_path7.default.resolve(input.cwd);
@@ -2326,7 +2527,7 @@ async function startSession(opts) {
2326
2527
  `session already running for project=${project.id} provider=${provider} (session ${existingLive.id}, pid ${existingLive.wrapperPid}). end it first with \`rnx agent end <sessionId>\`.`
2327
2528
  );
2328
2529
  }
2329
- const claudeSessionUuid = provider === "claude" ? (0, import_node_crypto3.randomUUID)() : void 0;
2530
+ const claudeSessionUuid = provider === "claude" ? (0, import_node_crypto4.randomUUID)() : void 0;
2330
2531
  const session = upsertSession({
2331
2532
  projectId: project.id,
2332
2533
  provider,
@@ -3120,15 +3321,6 @@ var import_http2 = __toESM(require("http"), 1);
3120
3321
  var import_https = __toESM(require("https"), 1);
3121
3322
  var import_promises4 = require("stream/promises");
3122
3323
 
3123
- // src/backend-origin.ts
3124
- function normalizeHostname(hostname) {
3125
- return hostname.replace(/^\[|\]$/g, "").toLowerCase();
3126
- }
3127
- function isLoopbackHost(hostname) {
3128
- const normalized = normalizeHostname(hostname);
3129
- return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
3130
- }
3131
-
3132
3324
  // src/host/fetch-proxy-overrides.ts
3133
3325
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
3134
3326
  var HOST_HEADER_OVERRIDES = [
@@ -3594,9 +3786,18 @@ export const Appearance = rn.Appearance
3594
3786
  `;
3595
3787
  }
3596
3788
  function sendJavaScript(req, res, source) {
3789
+ const origin = req.headers.origin;
3790
+ let allowedOrigin;
3791
+ if (origin) {
3792
+ try {
3793
+ if (isLoopbackHost(new URL(origin).hostname)) allowedOrigin = origin;
3794
+ } catch {
3795
+ }
3796
+ }
3597
3797
  res.writeHead(200, {
3598
3798
  "Content-Type": "application/javascript; charset=utf-8",
3599
- "Cache-Control": "no-cache"
3799
+ "Cache-Control": "no-cache",
3800
+ ...allowedOrigin ? { "Access-Control-Allow-Origin": allowedOrigin, Vary: "Origin" } : {}
3600
3801
  });
3601
3802
  res.end(req.method === "HEAD" ? void 0 : source);
3602
3803
  }
@@ -3617,6 +3818,23 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3617
3818
  } catch {
3618
3819
  return false;
3619
3820
  }
3821
+ if (options.requireLoopbackOrigin) {
3822
+ const origin = req.headers.origin;
3823
+ const remoteAddress = req.socket.remoteAddress?.replace(/^::ffff:/, "");
3824
+ let allowed = Boolean(remoteAddress && isLoopbackHost(remoteAddress));
3825
+ if (allowed && origin) {
3826
+ try {
3827
+ allowed = isLoopbackHost(new URL(origin).hostname);
3828
+ } catch {
3829
+ allowed = false;
3830
+ }
3831
+ }
3832
+ if (!allowed) {
3833
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3834
+ res.end("replacement modules are available only to loopback origins");
3835
+ return true;
3836
+ }
3837
+ }
3620
3838
  if (url.pathname === `${REPLACEMENT_RUNTIME_PREFIX}/react`) {
3621
3839
  sendJavaScript(req, res, reactRuntimeSource(options.reactBridgeUrl));
3622
3840
  return true;
@@ -3626,6 +3844,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3626
3844
  return true;
3627
3845
  }
3628
3846
  if (url.pathname !== REPLACEMENT_MODULE_ROUTE) return false;
3847
+ if (options.requestToken && url.searchParams.get("token") !== options.requestToken) {
3848
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3849
+ res.end("replacement module request token is invalid");
3850
+ return true;
3851
+ }
3629
3852
  const targetPath = url.searchParams.get("path");
3630
3853
  if (!targetPath) {
3631
3854
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
@@ -3638,17 +3861,42 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3638
3861
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
3639
3862
  return true;
3640
3863
  }
3864
+ if (!import_node_path10.default.isAbsolute(targetPath)) {
3865
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
3866
+ res.end("replacement file path must be absolute");
3867
+ return true;
3868
+ }
3869
+ let resolvedProjectRoot;
3870
+ if (options.projectRoot) {
3871
+ const lexicalProjectRoot = import_node_path10.default.resolve(options.projectRoot);
3872
+ resolvedProjectRoot = await import_promises5.default.realpath(options.projectRoot);
3873
+ const lexicalTarget = import_node_path10.default.relative(lexicalProjectRoot, targetPath);
3874
+ if (lexicalTarget.startsWith("..") || import_node_path10.default.isAbsolute(lexicalTarget)) {
3875
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3876
+ res.end("replacement file must be inside the project root");
3877
+ return true;
3878
+ }
3879
+ }
3880
+ let resolvedTargetPath;
3641
3881
  try {
3642
- await import_promises5.default.access(targetPath);
3882
+ resolvedTargetPath = await import_promises5.default.realpath(targetPath);
3643
3883
  } catch (error) {
3644
3884
  const message = error instanceof Error ? error.message : String(error);
3645
3885
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
3646
3886
  res.end(`replacement file not found: ${message}`);
3647
3887
  return true;
3648
3888
  }
3889
+ if (resolvedProjectRoot) {
3890
+ const relativeTarget = import_node_path10.default.relative(resolvedProjectRoot, resolvedTargetPath);
3891
+ if (relativeTarget.startsWith("..") || import_node_path10.default.isAbsolute(relativeTarget)) {
3892
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3893
+ res.end("replacement file must be inside the project root");
3894
+ return true;
3895
+ }
3896
+ }
3649
3897
  try {
3650
3898
  const result = await (0, import_esbuild.build)({
3651
- entryPoints: [targetPath],
3899
+ entryPoints: [resolvedTargetPath],
3652
3900
  bundle: true,
3653
3901
  write: false,
3654
3902
  format: "esm",
@@ -4911,7 +5159,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
4911
5159
  removeLockfile() {
4912
5160
  if (!this.shouldWriteLockfile) return;
4913
5161
  try {
4914
- removeDaemonLockfile();
5162
+ removeDaemonLockfile({ pid: process.pid, startedAt: this.startedAt });
4915
5163
  } catch {
4916
5164
  }
4917
5165
  }
@@ -5470,7 +5718,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
5470
5718
  }
5471
5719
  if (this.shouldWriteLockfile) {
5472
5720
  try {
5473
- removeDaemonLockfile();
5721
+ removeDaemonLockfile({ pid: process.pid, startedAt: this.startedAt });
5474
5722
  } catch {
5475
5723
  }
5476
5724
  }