@xopcai/xopc 0.0.270 → 0.0.273

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 (255) hide show
  1. package/README.md +1 -5
  2. package/README.zh-CN.md +1 -5
  3. package/dist/browser-ext/dist/assets/{auth-cwEj8rFb.js → auth-DkwOUI6_.js} +132 -33
  4. package/dist/browser-ext/dist/background.js +2 -2
  5. package/dist/browser-ext/dist/sidepanel.html +1 -1
  6. package/dist/browser-ext/dist/sidepanel.js +46 -32
  7. package/dist/browser-ext/manifest.json +1 -1
  8. package/dist/extensions/telegram/xopc.extension.json +1 -1
  9. package/dist/gateway/static/root/assets/{activity-store-D_0FmdRj.js → activity-store-cF7IX2RP.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-IZvEXak_.js → agent-avatar-dicebear-BrgxX8nG.js} +1 -1
  11. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-DvhuRCLA.js +1 -0
  12. package/dist/gateway/static/root/assets/agents-C0By2FIo.js +1 -0
  13. package/dist/gateway/static/root/assets/{api-O3TTQ4qN.js → api-CYkdUpkx.js} +1 -1
  14. package/dist/gateway/static/root/assets/app-link-BiBXVEvp.js +1 -0
  15. package/dist/gateway/static/root/assets/{app-management-settings-panel-Cqqsbzri.js → app-management-settings-panel-DfoNzWIa.js} +1 -1
  16. package/dist/gateway/static/root/assets/{appearance-settings-DaJyf32s.js → appearance-settings-czbLdCqj.js} +1 -1
  17. package/dist/gateway/static/root/assets/apps-page-BSJXKWcE.js +1 -0
  18. package/dist/gateway/static/root/assets/{archive-plugin-kDPnDTJI.js → archive-plugin-D14P13mX.js} +1 -1
  19. package/dist/gateway/static/root/assets/{attachment-load-CJ3VceKS.js → attachment-load-Dok_B1Gv.js} +1 -1
  20. package/dist/gateway/static/root/assets/{automations-page-JwMtU8XB.js → automations-page-DM7ZEewI.js} +1 -1
  21. package/dist/gateway/static/root/assets/automations-workspace--1TsZf3O.js +6 -0
  22. package/dist/gateway/static/root/assets/{binary-plugins-DzyZXLBX.js → binary-plugins-CxNg6InF.js} +1 -1
  23. package/dist/gateway/static/root/assets/{block-editor-kTt3gYIJ.js → block-editor-D22VcYnC.js} +3 -3
  24. package/dist/gateway/static/root/assets/{browser-automation-inputs-BKohPYR4.js → browser-automation-inputs-6NWTLEpd.js} +1 -1
  25. package/dist/gateway/static/root/assets/{browser-automations-page-ktQfFPk-.js → browser-automations-page-BrqySdbW.js} +2 -2
  26. package/dist/gateway/static/root/assets/browser-settings-page-DOHgQer_.js +8 -0
  27. package/dist/gateway/static/root/assets/capabilities-settings-panel-Bpse_5K-.js +8 -0
  28. package/dist/gateway/static/root/assets/channels-settings-yHsY174W.js +1 -0
  29. package/dist/gateway/static/root/assets/{channels-status-swr-DrqzYmDr.js → channels-status-swr-BUZimNmp.js} +1 -1
  30. package/dist/gateway/static/root/assets/{chat-terminal-dock-CIFGGLGa.js → chat-terminal-dock-CZPBNpMI.js} +2 -2
  31. package/dist/gateway/static/root/assets/clock-3-DnrAnuyZ.js +1 -0
  32. package/dist/gateway/static/root/assets/connectors-page-B_wLKGnH.js +2 -0
  33. package/dist/gateway/static/root/assets/cron-expression-C26e13YU.js +2 -0
  34. package/dist/gateway/static/root/assets/date-picker-Da_0QyLe.js +1 -0
  35. package/dist/gateway/static/root/assets/{dependency-picker-D3vNIjRI.js → dependency-picker-D_djqzy7.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-fQwRb_3p.js → desktop-pet-Celmbw-T.js} +1 -1
  37. package/dist/gateway/static/root/assets/{desktop-pet-settings-DfOJ-GvT.js → desktop-pet-settings-CqNNgF45.js} +1 -1
  38. package/dist/gateway/static/root/assets/device-pairing-wizard-B5_w9qlm.js +1 -0
  39. package/dist/gateway/static/root/assets/{extension-debug-page-Cn53jB5-.js → extension-debug-page-ku3kCjvH.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-page-IDpLCxpC.js → extension-page-C7FpHwL4.js} +1 -1
  41. package/dist/gateway/static/root/assets/extension-settings-page-OMj2tU2_.js +1 -0
  42. package/dist/gateway/static/root/assets/{gateway-config-swr-B7dcmP7Q.js → gateway-config-swr-6eb1vKE1.js} +1 -1
  43. package/dist/gateway/static/root/assets/gateway-settings-CH-6vkgN.js +1 -0
  44. package/dist/gateway/static/root/assets/{gateway-startup-retry-JGhGMhVZ.js → gateway-startup-retry-DUFFCU8H.js} +1 -1
  45. package/dist/gateway/static/root/assets/git-branch-Dh9uxTot.js +1 -0
  46. package/dist/gateway/static/root/assets/heartbeat-settings-CSFzOACM.js +1 -0
  47. package/dist/gateway/static/root/assets/home-page-BJebAKB-.js +1 -0
  48. package/dist/gateway/static/root/assets/{image-generation-api-BJLX7Qrh.js → image-generation-api-C1cHRBRp.js} +1 -1
  49. package/dist/gateway/static/root/assets/index-C6OWYFtQ.js +74 -0
  50. package/dist/gateway/static/root/assets/index-DKy1Qn1_.css +2 -0
  51. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CBvMTFFx.js → keyboard-shortcuts-settings-BuvD3iv8.js} +1 -1
  52. package/dist/gateway/static/root/assets/local-app-workbench-page-CaC4-THD.js +2 -0
  53. package/dist/gateway/static/root/assets/{local-apps-page-APCMNDvU.js → local-apps-page-DdJ8zwBp.js} +1 -1
  54. package/dist/gateway/static/root/assets/locale-store-BBSCIl4I.js +1 -0
  55. package/dist/gateway/static/root/assets/logs-page-BaR2he0K.js +2 -0
  56. package/dist/gateway/static/root/assets/management-settings-r-iYWZFY.js +1 -0
  57. package/dist/gateway/static/root/assets/markdown-view-c5vtnYe8.js +201 -0
  58. package/dist/gateway/static/root/assets/{media-plugins-C10byeaa.js → media-plugins-D_a6GDY6.js} +1 -1
  59. package/dist/gateway/static/root/assets/message-circle-Badhj-4D.js +1 -0
  60. package/dist/gateway/static/root/assets/message-square-fVE7Ghj1.js +1 -0
  61. package/dist/gateway/static/root/assets/{messages-D0V_bgRP.js → messages-DjuBdDCn.js} +3 -3
  62. package/dist/gateway/static/root/assets/note-detail-panel-BV6nbgac.js +5 -0
  63. package/dist/gateway/static/root/assets/notes-page-BmLKoWwi.js +8 -0
  64. package/dist/gateway/static/root/assets/{page-tabs-qQ36zncF.js → page-tabs-C7Le9bSI.js} +1 -1
  65. package/dist/gateway/static/root/assets/popover-select-Dz_Qis2R.js +1 -0
  66. package/dist/gateway/static/root/assets/{preview-open-alternatives-BiAJvvCo.js → preview-open-alternatives-B2pOxJBK.js} +1 -1
  67. package/dist/gateway/static/root/assets/{proactive-card-CcNBqHoH.js → proactive-card-BlQ1vCtV.js} +1 -1
  68. package/dist/gateway/static/root/assets/{proactive-page-C3K61jve.js → proactive-page-CghKTfYa.js} +1 -1
  69. package/dist/gateway/static/root/assets/{product-open-page-CunnDP4w.js → product-open-page-DpjiHgwu.js} +1 -1
  70. package/dist/gateway/static/root/assets/project-detail-page-CV8Lh296.js +1 -0
  71. package/dist/gateway/static/root/assets/projects-page-COweOaJP.js +1 -0
  72. package/dist/gateway/static/root/assets/{registry-api-0JSegbNl.js → registry-api-CyQ4k1Ux.js} +1 -1
  73. package/dist/gateway/static/root/assets/remote-access-hub-ChPPGVU5.js +1 -0
  74. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-bu_mwBTr.js → runtime-tools-settings-panel-Bl4ArLHf.js} +2 -2
  75. package/dist/gateway/static/root/assets/{schema-form-BE0sgZyz.js → schema-form-KQFmfWd4.js} +1 -1
  76. package/dist/gateway/static/root/assets/sessions-page-DgmHibOK.js +1 -0
  77. package/dist/gateway/static/root/assets/settings-advanced-gate-DV7kMco2.js +1 -0
  78. package/dist/gateway/static/root/assets/{settings-form-section-CBo517lV.js → settings-form-section-Dbnls9ty.js} +1 -1
  79. package/dist/gateway/static/root/assets/{settings-page-DBPimx93.js → settings-page-CDKirYRG.js} +1 -1
  80. package/dist/gateway/static/root/assets/setup-status-panel-SQmtWoOs.js +1 -0
  81. package/dist/gateway/static/root/assets/{share-preview-page-DdV75VTe.js → share-preview-page-DwgLjS1m.js} +1 -1
  82. package/dist/gateway/static/root/assets/shares-settings-Cs5bUrYw.js +1 -0
  83. package/dist/gateway/static/root/assets/skill-reload-api-DyKrjq7F.js +1 -0
  84. package/dist/gateway/static/root/assets/{skills-page-Cn_-nvRo.js → skills-page-Bd0_ED_p.js} +1 -1
  85. package/dist/gateway/static/root/assets/skills-page.utils-rA-XT-YG.js +1 -0
  86. package/dist/gateway/static/root/assets/system-settings-panel-CSkMrHLz.js +1 -0
  87. package/dist/gateway/static/root/assets/task-detail-page-CEQU-L_G.js +1 -0
  88. package/dist/gateway/static/root/assets/{theme-store-1wryLgBQ.js → theme-store-DWpIEWen.js} +1 -1
  89. package/dist/gateway/static/root/assets/tunnel-api-COJcGnEi.js +1 -0
  90. package/dist/gateway/static/root/assets/{use-autosave-N_lm4FdD.js → use-autosave-DkQAIAuB.js} +1 -1
  91. package/dist/gateway/static/root/assets/user-model-page-BQAU55lf.js +2 -0
  92. package/dist/gateway/static/root/assets/{voice-api-key-field-DMsyVfri.js → voice-api-key-field-BrqcH0P-.js} +1 -1
  93. package/dist/gateway/static/root/assets/{voice-config-api-QWVmiAp3.js → voice-config-api-BJ95Z3pT.js} +1 -1
  94. package/dist/gateway/static/root/assets/{work-discovery-overlay-BC0vAxp1.js → work-discovery-overlay-vfTWKm13.js} +1 -1
  95. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Cz9jQIlP.js → workflow-run-setup-panel-CHdr_ng9.js} +2 -2
  96. package/dist/gateway/static/root/assets/workflows-page-hwRJD7vR.js +6 -0
  97. package/dist/gateway/static/root/index.html +16 -11
  98. package/dist/package.js +1 -1
  99. package/dist/packages/gateway-contract/src/device-pairing.js +2 -1
  100. package/dist/packages/gateway-contract/src/session-identity.js +37 -0
  101. package/dist/src/agent/memory/compaction-chunks.d.ts +18 -0
  102. package/dist/src/agent/memory/compaction-chunks.js +53 -0
  103. package/dist/src/agent/memory/compaction-request.d.ts +12 -0
  104. package/dist/src/agent/memory/compaction-request.js +72 -0
  105. package/dist/src/agent/memory/compaction.d.ts +3 -0
  106. package/dist/src/agent/memory/compaction.js +166 -118
  107. package/dist/src/agent/service.js +1 -0
  108. package/dist/src/browser/native-messaging-host.js +3 -1
  109. package/dist/src/browser/providers/browser-ext-install.d.ts +6 -0
  110. package/dist/src/browser/providers/browser-ext-install.js +16 -1
  111. package/dist/src/cli/bin.js +2 -2
  112. package/dist/src/cli/commands/agent.js +2 -2
  113. package/dist/src/cli/commands/auth.js +1 -1
  114. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  115. package/dist/src/cli/commands/extension-dev.js +1 -1
  116. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  117. package/dist/src/cli/commands/extension.js +1 -1
  118. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  119. package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
  120. package/dist/src/cli/commands/gateway/service.js +4 -3
  121. package/dist/src/cli/commands/gateway/token.js +28 -1
  122. package/dist/src/cli/commands/gateway-ssh-tunnel.js +1 -1
  123. package/dist/src/cli/commands/models.js +2 -2
  124. package/dist/src/cli/commands/onboard.js +1 -1
  125. package/dist/src/cli/commands/platform.js +1 -1
  126. package/dist/src/cli/commands/profile.js +1 -1
  127. package/dist/src/cli/commands/tunnel.js +6 -1
  128. package/dist/src/cli/commands/update.js +1 -1
  129. package/dist/src/cli/extension-cli-register.js +1 -1
  130. package/dist/src/config/index.js +1 -1
  131. package/dist/src/gateway/device-routes.js +4 -3
  132. package/dist/src/gateway/heartbeat/service.js +1 -1
  133. package/dist/src/gateway/hono/app.js +1 -0
  134. package/dist/src/gateway/hono/middleware/auth.d.ts +1 -2
  135. package/dist/src/gateway/hono/middleware/auth.js +49 -31
  136. package/dist/src/gateway/hono/middleware/strict-rate-limit.js +11 -15
  137. package/dist/src/gateway/hono/routes/browser-session.d.ts +3 -0
  138. package/dist/src/gateway/hono/routes/browser-session.js +43 -0
  139. package/dist/src/gateway/hono/routes/browser.js +26 -0
  140. package/dist/src/gateway/hono/routes/devices.js +61 -96
  141. package/dist/src/gateway/hono/routes/exposure.js +3 -7
  142. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  143. package/dist/src/gateway/hono/routes/models.js +60 -1
  144. package/dist/src/gateway/hono/routes/notes.js +3 -7
  145. package/dist/src/gateway/hono/routes/sessions.js +11 -0
  146. package/dist/src/gateway/hono/routes/shares.js +8 -20
  147. package/dist/src/gateway/hono/routes/site-shares.js +4 -8
  148. package/dist/src/gateway/hono/routes/tunnel.js +13 -13
  149. package/dist/src/gateway/image-generation-setup.d.ts +17 -0
  150. package/dist/src/gateway/image-generation-setup.js +30 -21
  151. package/dist/src/gateway/rate-limit/buckets.d.ts +3 -0
  152. package/dist/src/gateway/rate-limit/buckets.js +10 -0
  153. package/dist/src/gateway/run-loop.js +1 -1
  154. package/dist/src/gateway/runtime-config.js +2 -0
  155. package/dist/src/gateway/security/browser-session.d.ts +5 -0
  156. package/dist/src/gateway/security/browser-session.js +45 -0
  157. package/dist/src/gateway/security/gateway-principal.d.ts +1 -0
  158. package/dist/src/gateway/server.js +1 -1
  159. package/dist/src/gateway/service.d.ts +1 -0
  160. package/dist/src/gateway/service.js +11 -1
  161. package/dist/src/heartbeat/index.js +1 -1
  162. package/dist/src/infra/rate-limit/keyed-store.d.ts +3 -0
  163. package/dist/src/infra/rate-limit/keyed-store.js +9 -2
  164. package/dist/src/proactive/policy/service.d.ts +1 -1
  165. package/dist/src/realtime/runtime.d.ts +2 -1
  166. package/dist/src/realtime/runtime.js +9 -3
  167. package/dist/src/realtime/tickets.d.ts +1 -0
  168. package/dist/src/realtime/tickets.js +3 -0
  169. package/dist/src/session/store.d.ts +2 -0
  170. package/dist/src/session/store.js +17 -2
  171. package/dist/src/session/types.d.ts +2 -1
  172. package/dist/src/storage/sqlite/browser-session-repository.d.ts +17 -0
  173. package/dist/src/storage/sqlite/browser-session-repository.js +54 -0
  174. package/dist/src/storage/sqlite/device-pairing-approval.js +3 -4
  175. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +4 -10
  176. package/dist/src/storage/sqlite/device-pairing-repository.js +10 -62
  177. package/dist/src/storage/sqlite/index.d.ts +1 -1
  178. package/dist/src/storage/sqlite/index.js +2 -2
  179. package/dist/src/storage/sqlite/migrations/164_browser_sessions.sql +8 -0
  180. package/dist/src/storage/sqlite/migrations/165_device_pairing_target.sql +40 -0
  181. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  182. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  183. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  184. package/dist/src/storage/sqlite/schema.js +1 -1
  185. package/dist/src/storage/sqlite/session-identity-sql.d.ts +2 -0
  186. package/dist/src/storage/sqlite/session-identity-sql.js +18 -0
  187. package/dist/src/storage/sqlite/session-repository.js +26 -22
  188. package/dist/src/tunnel/auth-policy.d.ts +3 -0
  189. package/dist/src/tunnel/auth-policy.js +8 -0
  190. package/dist/src/tunnel/broker-client.d.ts +5 -15
  191. package/dist/src/tunnel/broker-client.js +37 -49
  192. package/dist/src/tunnel/frpc-binary.d.ts +8 -6
  193. package/dist/src/tunnel/frpc-binary.js +76 -88
  194. package/dist/src/tunnel/frpc-config.d.ts +2 -1
  195. package/dist/src/tunnel/frpc-config.js +29 -12
  196. package/dist/src/tunnel/frpc-release.d.ts +27 -0
  197. package/dist/src/tunnel/frpc-release.js +30 -0
  198. package/dist/src/tunnel/gateway-lifecycle.js +8 -2
  199. package/dist/src/tunnel/index.js +2 -1
  200. package/dist/src/tunnel/registration-proof.d.ts +19 -0
  201. package/dist/src/tunnel/registration-proof.js +37 -0
  202. package/dist/src/tunnel/tunnel-persist.js +3 -1
  203. package/dist/src/tunnel/tunnel-service.d.ts +1 -0
  204. package/dist/src/tunnel/tunnel-service.js +35 -8
  205. package/dist/src/tunnel/tunnel-state.js +11 -5
  206. package/dist/src/tunnel/tunnel-types.d.ts +2 -0
  207. package/dist/src/tunnel/well-known.js +5 -1
  208. package/dist/src/user-context/config.js +2 -2
  209. package/dist/src/utils/logger/redact.js +4 -0
  210. package/dist/src/voice/realtime/runtime.d.ts +1 -0
  211. package/dist/src/voice/realtime/runtime.js +17 -1
  212. package/dist/src/voice/realtime/runtime.types.d.ts +1 -0
  213. package/dist/src/workflows/service/workflow-run-service.js +2 -0
  214. package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
  215. package/dist/src/workflows/service/workflow-session-bridge.js +1 -0
  216. package/package.json +3 -3
  217. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CE3ygr3l.js +0 -1
  218. package/dist/gateway/static/root/assets/agents-DzG2qw-c.js +0 -1
  219. package/dist/gateway/static/root/assets/apps-page-B4lX6yoT.js +0 -1
  220. package/dist/gateway/static/root/assets/automations-workspace-BiNBewg0.js +0 -6
  221. package/dist/gateway/static/root/assets/browser-settings-page-Dgdt5uVL.js +0 -8
  222. package/dist/gateway/static/root/assets/capabilities-settings-panel-BpDW-R9f.js +0 -7
  223. package/dist/gateway/static/root/assets/channels-settings-DU4EQnR0.js +0 -1
  224. package/dist/gateway/static/root/assets/connectors-page-CfJqy284.js +0 -2
  225. package/dist/gateway/static/root/assets/cron-expression-BNwbnrEb.js +0 -2
  226. package/dist/gateway/static/root/assets/date-picker-DKyirU4K.js +0 -1
  227. package/dist/gateway/static/root/assets/extension-settings-page-B1dds2bP.js +0 -1
  228. package/dist/gateway/static/root/assets/gateway-settings-pp9jN0wl.js +0 -1
  229. package/dist/gateway/static/root/assets/heartbeat-settings-B8_zTyev.js +0 -1
  230. package/dist/gateway/static/root/assets/home-page-g1KdAdLb.js +0 -1
  231. package/dist/gateway/static/root/assets/index-1zRt56Zt.css +0 -2
  232. package/dist/gateway/static/root/assets/index-OMnem2nr.js +0 -74
  233. package/dist/gateway/static/root/assets/local-app-workbench-page-bj75v2ZE.js +0 -2
  234. package/dist/gateway/static/root/assets/locale-store-BThkG0W5.js +0 -1
  235. package/dist/gateway/static/root/assets/logs-page-BX7n39zV.js +0 -2
  236. package/dist/gateway/static/root/assets/management-settings-CM62j8ZX.js +0 -1
  237. package/dist/gateway/static/root/assets/markdown-view-BwjpJVPs.js +0 -201
  238. package/dist/gateway/static/root/assets/mobile-pairing-wizard-BCWfwVx3.js +0 -1
  239. package/dist/gateway/static/root/assets/note-detail-panel-koOqb0gX.js +0 -5
  240. package/dist/gateway/static/root/assets/notes-page-BH54rY3B.js +0 -8
  241. package/dist/gateway/static/root/assets/popover-select-CKBYnT3c.js +0 -1
  242. package/dist/gateway/static/root/assets/project-detail-page-SL2g8MZY.js +0 -1
  243. package/dist/gateway/static/root/assets/projects-page-BtxazT5J.js +0 -1
  244. package/dist/gateway/static/root/assets/remote-access-hub-BSb7b67A.js +0 -1
  245. package/dist/gateway/static/root/assets/sessions-page-ljcS2KVz.js +0 -1
  246. package/dist/gateway/static/root/assets/settings-advanced-gate-CR9Y9LQm.js +0 -1
  247. package/dist/gateway/static/root/assets/setup-status-panel-J3AnooPW.js +0 -1
  248. package/dist/gateway/static/root/assets/shares-settings-AAm4Iwcp.js +0 -1
  249. package/dist/gateway/static/root/assets/skill-reload-api-pRCjP5sm.js +0 -1
  250. package/dist/gateway/static/root/assets/skills-page.utils-CfVAc_lu.js +0 -1
  251. package/dist/gateway/static/root/assets/system-settings-panel-B-pzalp-.js +0 -1
  252. package/dist/gateway/static/root/assets/task-detail-page-CmrLy3PO.js +0 -1
  253. package/dist/gateway/static/root/assets/tunnel-api-Cgpnm2fL.js +0 -1
  254. package/dist/gateway/static/root/assets/user-model-page-u3QLDX6L.js +0 -2
  255. package/dist/gateway/static/root/assets/workflows-page-DA6OOK6X.js +0 -6
@@ -2,127 +2,115 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveBinDir } from "../config/paths.js";
4
4
  import { extractFrpcFromReleaseArchive, frpcReleaseArchiveExtension, nodePlatformForFrpTarget } from "./frpc-extract.js";
5
- import { tmpdir } from "node:os";
5
+ import { FRPC_RELEASES, FRPC_VERSION } from "./frpc-release.js";
6
6
  import { join } from "node:path";
7
- import { chmodSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
8
- import { randomBytes } from "node:crypto";
7
+ import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync } from "node:fs";
8
+ import { createHash } from "node:crypto";
9
9
  import { pipeline } from "node:stream/promises";
10
- import { Readable } from "node:stream";
10
+ import { Readable, Transform } from "node:stream";
11
11
  //#region src/tunnel/frpc-binary.ts
12
12
  init_paths();
13
13
  init_logger();
14
14
  const log = createLogger("TunnelFrpc");
15
- const FRPC_VERSION = "0.62.1";
16
- const PLATFORM_MAP = {
17
- darwin: "darwin",
18
- linux: "linux",
19
- win32: "windows"
20
- };
21
- const ARCH_MAP = {
22
- x64: "amd64",
23
- arm64: "arm64",
24
- ia32: "386"
25
- };
26
- function buildFrpcReleaseBasename(frpPlatform, arch, version = FRPC_VERSION) {
27
- return `frp_${version}_${frpPlatform}_${arch}`;
15
+ const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
16
+ function buildFrpcReleaseBasename(platform, arch, version = FRPC_VERSION) {
17
+ return `frp_${version}_${platform}_${arch}`;
28
18
  }
29
- function frpcDownloadUrlsForTarget(frpPlatform, arch, version = FRPC_VERSION) {
30
- const folder = buildFrpcReleaseBasename(frpPlatform, arch, version);
31
- const ext = frpcReleaseArchiveExtension(frpPlatform);
32
- return [
33
- `https://frp.xopc.ai/bin/${folder}${ext}`,
34
- `https://github.com/fatedier/frp/releases/download/v${version}/${folder}${ext}`,
35
- `https://ghfast.top/https://github.com/fatedier/frp/releases/download/v${version}/${folder}${ext}`
36
- ];
19
+ function frpcDownloadUrlsForTarget(platform, arch, version = FRPC_VERSION) {
20
+ const archive = buildFrpcReleaseBasename(platform, arch, version) + frpcReleaseArchiveExtension(platform);
21
+ return [`https://frp.xopc.ai/bin/${archive}`, `https://github.com/fatedier/frp/releases/download/v${version}/${archive}`];
37
22
  }
38
- function frpcPlatformArch() {
39
- const platform = PLATFORM_MAP[process.platform] ?? process.platform;
40
- const arch = ARCH_MAP[process.arch] ?? process.arch;
41
- return {
42
- platform,
43
- arch,
44
- folder: buildFrpcReleaseBasename(platform, arch)
45
- };
23
+ async function verifyFrpcDigest(path, expected) {
24
+ const hash = createHash("sha256");
25
+ await pipeline(createReadStream(path), hash);
26
+ if (hash.digest("hex") !== expected) throw new Error("COMPONENT_VERIFY_FAILED: frpc checksum mismatch");
46
27
  }
47
- function frpcDownloadUrls() {
48
- const { platform, arch } = frpcPlatformArch();
49
- return frpcDownloadUrlsForTarget(platform, arch);
50
- }
51
- async function downloadToFile(url, destPath, onProgress) {
52
- const res = await fetch(url);
53
- if (!res.ok || !res.body) throw new Error(`Download failed: ${res.status} ${url}`);
54
- const contentLength = res.headers.get("content-length");
55
- const totalBytes = contentLength && Number.isFinite(Number(contentLength)) ? Number(contentLength) : null;
28
+ async function downloadToFile(url, path, onProgress) {
29
+ const response = await fetch(url, { signal: AbortSignal.timeout(6e4) });
30
+ if (!response.ok || !response.body) throw new Error(`frpc download failed: ${response.status}`);
31
+ const length = Number(response.headers.get("content-length"));
32
+ const totalBytes = length > 0 ? length : null;
33
+ if (totalBytes && totalBytes > MAX_ARCHIVE_BYTES) {
34
+ await response.body.cancel();
35
+ throw new Error("frpc archive exceeds size limit");
36
+ }
56
37
  let bytesReceived = 0;
57
- const report = () => {
38
+ const bounded = new Transform({ transform(chunk, _encoding, callback) {
39
+ bytesReceived += chunk.length;
40
+ if (bytesReceived > MAX_ARCHIVE_BYTES) return callback(/* @__PURE__ */ new Error("frpc archive exceeds size limit"));
58
41
  onProgress?.({
59
42
  phase: "downloading",
60
43
  url,
61
44
  bytesReceived,
62
45
  totalBytes,
63
- percent: totalBytes && totalBytes > 0 ? Math.min(100, Math.round(bytesReceived / totalBytes * 100)) : null
46
+ percent: totalBytes ? Math.min(100, Math.round(bytesReceived / totalBytes * 100)) : null
64
47
  });
65
- };
66
- report();
67
- const nodeStream = Readable.fromWeb(res.body);
68
- nodeStream.on("data", (chunk) => {
69
- bytesReceived += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
70
- report();
71
- });
72
- await pipeline(nodeStream, createWriteStream(destPath));
73
- report();
48
+ callback(null, chunk);
49
+ } });
50
+ await pipeline(Readable.fromWeb(response.body), bounded, createWriteStream(path, { mode: 384 }));
74
51
  }
75
- /** Set after a successful tunnel start so subprocesses can resolve the same binary. */
76
- function publishFrpcPathForProcess(binPath) {
77
- process.env.XOPC_FRPC_PATH = binPath;
52
+ function publishFrpcPathForProcess(path) {
53
+ process.env.XOPC_FRPC_PATH = path;
78
54
  }
79
- /** Clear runtime frpc path when the tunnel stops (no bundled Electron binary). */
80
55
  function clearFrpcPathForProcess() {
81
56
  delete process.env.XOPC_FRPC_PATH;
82
57
  }
83
- async function ensureFrpcBinary(opts) {
84
- const onProgress = opts?.onProgress;
85
- const binName = `frpc${process.platform === "win32" ? ".exe" : ""}`;
86
- const fromEnv = process.env.XOPC_FRPC_PATH?.trim();
87
- if (fromEnv && existsSync(fromEnv)) return fromEnv;
88
- const cacheDir = resolveBinDir();
89
- mkdirSync(cacheDir, { recursive: true });
58
+ async function ensureFrpcBinary(opts = {}) {
59
+ const platform = opts.platform ?? (process.platform === "win32" ? "windows" : process.platform);
60
+ const arch = opts.arch ?? (process.arch === "x64" ? "amd64" : process.arch);
61
+ const target = `${platform}_${arch}`;
62
+ if (!Object.hasOwn(FRPC_RELEASES, target)) throw new Error(`Unsupported frpc target: ${target}`);
63
+ const release = FRPC_RELEASES[target];
64
+ const fromEnv = !opts.platform && !opts.arch ? process.env.XOPC_FRPC_PATH?.trim() : void 0;
65
+ if (fromEnv) {
66
+ await verifyFrpcDigest(fromEnv, release.binary);
67
+ return fromEnv;
68
+ }
69
+ const cacheDir = join(resolveBinDir(), "frpc", FRPC_VERSION, target);
70
+ mkdirSync(cacheDir, {
71
+ recursive: true,
72
+ mode: 448
73
+ });
74
+ chmodSync(cacheDir, 448);
75
+ const binName = platform === "windows" ? "frpc.exe" : "frpc";
90
76
  const cachePath = join(cacheDir, binName);
91
- if (existsSync(cachePath)) return cachePath;
92
- const tmpBase = join(tmpdir(), `xopc-frpc-${randomBytes(6).toString("hex")}`);
93
- mkdirSync(tmpBase, { recursive: true });
94
- const { platform, arch, folder } = frpcPlatformArch();
95
- const archivePath = join(tmpBase, `frpc${frpcReleaseArchiveExtension(platform)}`);
96
- const urls = frpcDownloadUrls();
97
- let lastErr;
77
+ if (existsSync(cachePath)) try {
78
+ await verifyFrpcDigest(cachePath, release.binary);
79
+ return cachePath;
80
+ } catch {
81
+ log.warn({ target }, "Discarding unverified frpc cache");
82
+ }
83
+ const temporary = mkdtempSync(join(cacheDir, ".download-"));
84
+ const archivePath = join(temporary, "release" + frpcReleaseArchiveExtension(platform));
85
+ const executablePath = join(temporary, binName);
86
+ let lastError;
98
87
  try {
99
- for (const url of urls) try {
100
- log.info({ url }, "Downloading frpc");
101
- await downloadToFile(url, archivePath, onProgress);
102
- onProgress?.({
88
+ for (const url of frpcDownloadUrlsForTarget(platform, arch)) try {
89
+ await downloadToFile(url, archivePath, opts.onProgress);
90
+ await verifyFrpcDigest(archivePath, release.archive);
91
+ opts.onProgress?.({
103
92
  phase: "extracting",
104
- url,
105
- bytesReceived: 0,
106
- totalBytes: null,
107
- percent: null
93
+ url
108
94
  });
109
- await extractFrpcFromReleaseArchive(archivePath, cachePath, folder, nodePlatformForFrpTarget(platform));
110
- if (process.platform !== "win32") chmodSync(cachePath, 493);
95
+ await extractFrpcFromReleaseArchive(archivePath, executablePath, buildFrpcReleaseBasename(platform, arch), nodePlatformForFrpTarget(platform));
96
+ await verifyFrpcDigest(executablePath, release.binary);
97
+ if (platform !== "windows") chmodSync(executablePath, 448);
98
+ renameSync(executablePath, cachePath);
111
99
  return cachePath;
112
- } catch (err) {
113
- lastErr = err;
100
+ } catch (error) {
101
+ lastError = error;
114
102
  log.warn({
115
103
  url,
116
- err
117
- }, "frpc download attempt failed");
104
+ err: error
105
+ }, "Verified frpc download failed");
118
106
  }
119
107
  } finally {
120
- rmSync(tmpBase, {
108
+ rmSync(temporary, {
121
109
  recursive: true,
122
110
  force: true
123
111
  });
124
112
  }
125
- throw new Error(`Failed to download frpc v${FRPC_VERSION} for ${platform}/${arch}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
113
+ throw new Error(`Failed to obtain verified frpc for ${target}`, { cause: lastError });
126
114
  }
127
115
  //#endregion
128
- export { FRPC_VERSION, buildFrpcReleaseBasename, clearFrpcPathForProcess, ensureFrpcBinary, frpcDownloadUrlsForTarget, publishFrpcPathForProcess };
116
+ export { FRPC_VERSION, buildFrpcReleaseBasename, clearFrpcPathForProcess, ensureFrpcBinary, frpcDownloadUrlsForTarget, publishFrpcPathForProcess, verifyFrpcDigest };
@@ -1,3 +1,4 @@
1
1
  import type { TunnelRegistration } from './tunnel-types.js';
2
2
  export declare function resolveFrpcConfigPath(tunnelId: string): string;
3
- export declare function writeFrpcConfig(registration: TunnelRegistration, localPort: number, localHost?: string, mode?: 'http' | 'https'): string;
3
+ export declare function removeFrpcConfig(tunnelId: string): void;
4
+ export declare function writeFrpcConfig(registration: TunnelRegistration, localPort: number, localHost?: string): string;
@@ -1,34 +1,51 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { join } from "node:path";
4
- import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { rootCertificates } from "node:tls";
5
6
  //#region src/tunnel/frpc-config.ts
6
7
  init_paths();
7
8
  function resolveFrpcConfigPath(tunnelId) {
9
+ if (!/^[a-zA-Z0-9_-]+$/.test(tunnelId)) throw new Error("Invalid tunnel id");
8
10
  const tmpDir = join(resolveStateDir(), "tmp");
9
- mkdirSync(tmpDir, { recursive: true });
11
+ mkdirSync(tmpDir, {
12
+ recursive: true,
13
+ mode: 448
14
+ });
15
+ chmodSync(tmpDir, 448);
10
16
  return join(tmpDir, `frpc-${tunnelId}.toml`);
11
17
  }
12
- function writeFrpcConfig(registration, localPort, localHost = "127.0.0.1", mode = "http") {
18
+ function removeFrpcConfig(tunnelId) {
19
+ rmSync(resolveFrpcConfigPath(tunnelId), { force: true });
20
+ }
21
+ function writeFrpcConfig(registration, localPort, localHost = "127.0.0.1") {
13
22
  const configPath = resolveFrpcConfigPath(registration.tunnelId);
23
+ const caPath = join(resolveStateDir(), "tmp", "frp-trusted-roots.pem");
24
+ writeFileSync(caPath, rootCertificates.join("\n"), { mode: 384 });
14
25
  const { frpc } = registration;
26
+ for (const port of [localPort, frpc.serverPort]) if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid tunnel port");
27
+ const quote = JSON.stringify;
15
28
  writeFileSync(configPath, `# Auto-generated by xopc — do not edit
16
- serverAddr = "${frpc.serverAddr}"
29
+ serverAddr = ${quote(frpc.serverAddr)}
17
30
  serverPort = ${frpc.serverPort}
18
- metadatas.token = "${frpc.authToken}"
19
-
31
+ metadatas.token = ${quote(frpc.authToken)}
20
32
  loginFailExit = false
33
+ transport.tls.enable = true
34
+ transport.tls.serverName = ${quote(frpc.serverAddr)}
35
+ transport.tls.trustedCaFile = ${quote(caPath)}
21
36
  transport.heartbeatInterval = 10
22
37
  transport.heartbeatTimeout = 30
23
38
 
24
39
  [[proxies]]
25
- name = "${frpc.proxyName}"
26
- type = "${mode}"
27
- localIP = "${localHost}"
40
+ name = ${quote(frpc.proxyName)}
41
+ type = "http"
42
+ localIP = ${quote(localHost)}
28
43
  localPort = ${localPort}
29
- subdomain = "${registration.subdomain}"
30
- `, "utf8");
44
+ subdomain = ${quote(frpc.subdomain)}
45
+ hostHeaderRewrite = ${quote(new URL(registration.publicUrl).hostname)}
46
+ `, { mode: 384 });
47
+ chmodSync(configPath, 384);
31
48
  return configPath;
32
49
  }
33
50
  //#endregion
34
- export { resolveFrpcConfigPath, writeFrpcConfig };
51
+ export { removeFrpcConfig, resolveFrpcConfigPath, writeFrpcConfig };
@@ -0,0 +1,27 @@
1
+ export declare const FRPC_VERSION = "0.62.1";
2
+ export declare const FRPC_RELEASES: {
3
+ readonly darwin_amd64: {
4
+ readonly archive: "f951a5aa727a4880f32753ba3c41ecc9dee38a63658760354011e71ea83db995";
5
+ readonly binary: "5ce5258b6ff1a232e9eb8e29247a55badb127e7fc66b5a58c299b442aba2bcb2";
6
+ };
7
+ readonly darwin_arm64: {
8
+ readonly archive: "f9fc616d994a87d790504da21c2f942cd4224637d9ade9a67482f3c23c7f2432";
9
+ readonly binary: "49afde483f55927c3eeac9141cae82857cb2f15b9e9d55f4ac45378e761eabcc";
10
+ };
11
+ readonly linux_amd64: {
12
+ readonly archive: "dcf1c62e5862543b6686a8e4c2f430ef636ac33306167f377e7272875020c4d3";
13
+ readonly binary: "030bff369048a49fce09e44f96e71fab36522394675ab10b2e684c9e3cc0aa26";
14
+ };
15
+ readonly linux_arm64: {
16
+ readonly archive: "94c98c19ce181f8e09e0d0cb79c278c31e9ec936eb713826a1ddf4995e7c2155";
17
+ readonly binary: "3f900ac9b035aac50b117ce5f7c450ca073d3e453448783979e978dc57bc39a9";
18
+ };
19
+ readonly windows_amd64: {
20
+ readonly archive: "a12e3b36b81232ad9888b43519a6dbcacc5c8def52f3a6c6e95d4fae6a373c5d";
21
+ readonly binary: "14cd52ca87fea607852b3a8e194146eeabe6ce87976cd1fc6327b74caebb6c77";
22
+ };
23
+ readonly windows_arm64: {
24
+ readonly archive: "da9e79e7cacc5570631196fae2f9e50ae5202521aa8abaf2e6de85b1778086b9";
25
+ readonly binary: "14b1b4bdd75228dc29dbc3ca264602911242a0e60cb0acb3311eb4926d50cb4b";
26
+ };
27
+ };
@@ -0,0 +1,30 @@
1
+ //#region src/tunnel/frpc-release.ts
2
+ const FRPC_VERSION = "0.62.1";
3
+ const FRPC_RELEASES = {
4
+ "darwin_amd64": {
5
+ "archive": "f951a5aa727a4880f32753ba3c41ecc9dee38a63658760354011e71ea83db995",
6
+ "binary": "5ce5258b6ff1a232e9eb8e29247a55badb127e7fc66b5a58c299b442aba2bcb2"
7
+ },
8
+ "darwin_arm64": {
9
+ "archive": "f9fc616d994a87d790504da21c2f942cd4224637d9ade9a67482f3c23c7f2432",
10
+ "binary": "49afde483f55927c3eeac9141cae82857cb2f15b9e9d55f4ac45378e761eabcc"
11
+ },
12
+ "linux_amd64": {
13
+ "archive": "dcf1c62e5862543b6686a8e4c2f430ef636ac33306167f377e7272875020c4d3",
14
+ "binary": "030bff369048a49fce09e44f96e71fab36522394675ab10b2e684c9e3cc0aa26"
15
+ },
16
+ "linux_arm64": {
17
+ "archive": "94c98c19ce181f8e09e0d0cb79c278c31e9ec936eb713826a1ddf4995e7c2155",
18
+ "binary": "3f900ac9b035aac50b117ce5f7c450ca073d3e453448783979e978dc57bc39a9"
19
+ },
20
+ "windows_amd64": {
21
+ "archive": "a12e3b36b81232ad9888b43519a6dbcacc5c8def52f3a6c6e95d4fae6a373c5d",
22
+ "binary": "14cd52ca87fea607852b3a8e194146eeabe6ce87976cd1fc6327b74caebb6c77"
23
+ },
24
+ "windows_arm64": {
25
+ "archive": "da9e79e7cacc5570631196fae2f9e50ae5202521aa8abaf2e6de85b1778086b9",
26
+ "binary": "14b1b4bdd75228dc29dbc3ca264602911242a0e60cb0acb3311eb4926d50cb4b"
27
+ }
28
+ };
29
+ //#endregion
30
+ export { FRPC_RELEASES, FRPC_VERSION };
@@ -3,6 +3,8 @@ import { init_logger } from "../utils/logger.js";
3
3
  import { resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
4
4
  import { resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl } from "./env.js";
5
5
  import { getTunnelService } from "./tunnel-service.js";
6
+ import { resolveGatewayAuth } from "../gateway/auth.js";
7
+ import { requireTunnelGatewayToken } from "./auth-policy.js";
6
8
  import { hasValidTunnelConsent } from "./consent.js";
7
9
  import { resolveFrpSubdomainHost } from "./frp-subdomain-host.js";
8
10
  import { fetchTunnelWellKnown } from "./well-known.js";
@@ -99,7 +101,8 @@ async function maybeAutoStartTunnelFromConfig(config, gatewayToken) {
99
101
  const port = (config.gateway ?? {}).port ?? 18790;
100
102
  const host = resolveGatewayEffectiveHost(config);
101
103
  await configureTunnelFromGatewayConfig(config);
102
- if (!gatewayToken) {
104
+ const resolvedAuth = resolveGatewayAuth({ authConfig: config.gateway?.auth });
105
+ if (resolvedAuth.mode !== "token" || !gatewayToken?.trim()) {
103
106
  log.warn({ phase: "tunnel_autostart" }, "tunnel.autoStart is enabled but gateway auth token is unavailable (auth mode may be none)");
104
107
  return;
105
108
  }
@@ -113,7 +116,10 @@ async function maybeAutoStartTunnelFromConfig(config, gatewayToken) {
113
116
  return;
114
117
  }
115
118
  try {
116
- await tunnel.start(port, gatewayToken);
119
+ await tunnel.start(port, requireTunnelGatewayToken({
120
+ mode: resolvedAuth.mode,
121
+ token: gatewayToken
122
+ }));
117
123
  log.info({
118
124
  phase: "tunnel_autostart",
119
125
  host,
@@ -1,5 +1,6 @@
1
1
  import { TunnelBrokerClient, resolveBrokerApiBase } from "./broker-client.js";
2
- import { FRPC_VERSION, clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
2
+ import { FRPC_VERSION } from "./frpc-release.js";
3
+ import { clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
3
4
  import { TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TunnelRegistrationSecretError, getTunnelRegistrationSecretMeta, isLocalDevelopmentTunnelBroker, isMaskedTunnelSecretPatchValue, resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, tunnelRegistrationSecretRequiredMessage } from "./env.js";
4
5
  import { loadTunnelState, resolveTunnelStatePath, saveTunnelState } from "./tunnel-state.js";
5
6
  import { logTunnelAudit } from "./tunnel-audit.js";
@@ -0,0 +1,19 @@
1
+ /** Canonical v2 registration proof, shared with the Broker protocol test vectors. */
2
+ export declare function createTunnelRegistrationProof(input: {
3
+ brokerUrl: string;
4
+ platform: string;
5
+ gatewayVersion: string;
6
+ preferredSubdomain?: string;
7
+ recoveryToken?: string;
8
+ }): {
9
+ version: number;
10
+ gatewayId: string;
11
+ publicKey: string;
12
+ platform: string;
13
+ gatewayVersion: string;
14
+ preferredSubdomain: string;
15
+ recoveryToken: string;
16
+ nonce: string;
17
+ expiresAt: number;
18
+ signature: string;
19
+ };
@@ -0,0 +1,37 @@
1
+ import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity, signGatewayPayload } from "../storage/sqlite/gateway-identity-repository.js";
2
+ import { randomBytes } from "node:crypto";
3
+ //#region src/tunnel/registration-proof.ts
4
+ /** Canonical v2 registration proof, shared with the Broker protocol test vectors. */
5
+ function createTunnelRegistrationProof(input) {
6
+ const identity = getOrCreateGatewayIdentity();
7
+ const publicKey = getGatewayIdentityPublicKeyRaw(identity);
8
+ const nonce = randomBytes(24).toString("base64url");
9
+ const expiresAt = Date.now() + 12e4;
10
+ const audience = input.brokerUrl.replace(/\/+$/, "");
11
+ const payload = JSON.stringify([
12
+ "xopc-tunnel-registration-v2",
13
+ audience,
14
+ identity.id,
15
+ publicKey,
16
+ input.platform,
17
+ input.gatewayVersion,
18
+ input.preferredSubdomain ?? "",
19
+ input.recoveryToken ?? "",
20
+ nonce,
21
+ expiresAt
22
+ ]);
23
+ return {
24
+ version: 2,
25
+ gatewayId: identity.id,
26
+ publicKey,
27
+ platform: input.platform,
28
+ gatewayVersion: input.gatewayVersion,
29
+ preferredSubdomain: input.preferredSubdomain,
30
+ recoveryToken: input.recoveryToken,
31
+ nonce,
32
+ expiresAt,
33
+ signature: signGatewayPayload(payload)
34
+ };
35
+ }
36
+ //#endregion
37
+ export { createTunnelRegistrationProof };
@@ -8,6 +8,7 @@ function persistedFromRegistration(registration) {
8
8
  frpcAuthToken: registration.frpc.authToken,
9
9
  registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
10
10
  enabled: true,
11
+ frpcSubdomain: registration.frpc.subdomain,
11
12
  frpcServerAddr: registration.frpc.serverAddr,
12
13
  frpcServerPort: registration.frpc.serverPort,
13
14
  proxyName: registration.frpc.proxyName,
@@ -16,13 +17,14 @@ function persistedFromRegistration(registration) {
16
17
  }
17
18
  /** Rebuild registration from disk when Broker row is still valid (stop without deregister). */
18
19
  function registrationFromPersisted(persisted) {
19
- if (!persisted.frpcAuthToken || !persisted.frpcServerAddr || !persisted.frpcServerPort || !persisted.proxyName) return null;
20
+ if (!persisted.frpcSubdomain || !persisted.frpcAuthToken || !persisted.frpcServerAddr || !persisted.frpcServerPort || !persisted.proxyName) return null;
20
21
  return {
21
22
  tunnelId: persisted.tunnelId,
22
23
  tunnelToken: persisted.tunnelToken,
23
24
  subdomain: persisted.subdomain,
24
25
  publicUrl: persisted.publicUrl,
25
26
  frpc: {
27
+ subdomain: persisted.frpcSubdomain,
26
28
  serverAddr: persisted.frpcServerAddr,
27
29
  serverPort: persisted.frpcServerPort,
28
30
  authToken: persisted.frpcAuthToken,
@@ -37,6 +37,7 @@ export declare class TunnelService extends EventEmitter {
37
37
  private spawnAndWait;
38
38
  private scheduleReconnect;
39
39
  private runReconnectLoop;
40
+ private heartbeatGeneration;
40
41
  private startHeartbeat;
41
42
  private clearHeartbeat;
42
43
  }
@@ -1,9 +1,9 @@
1
1
  import { PACKAGE_VERSION, init_package_version } from "../package-version.js";
2
2
  import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
- import { TunnelBrokerClient, resolveBrokerApiBase } from "./broker-client.js";
4
+ import { TunnelBrokerClient, TunnelBrokerError, resolveBrokerApiBase } from "./broker-client.js";
5
5
  import { clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
6
- import { writeFrpcConfig } from "./frpc-config.js";
6
+ import { removeFrpcConfig, writeFrpcConfig } from "./frpc-config.js";
7
7
  import { spawnFrpcProcess } from "./frpc-process.js";
8
8
  import { TunnelRegistrationSecretError } from "./env.js";
9
9
  import { canResumePersistedTunnel, persistedFromRegistration, registrationFromPersisted } from "./tunnel-persist.js";
@@ -127,7 +127,7 @@ var TunnelService = class extends EventEmitter {
127
127
  saveTunnelState(state);
128
128
  this.setStartPhase("registering", { publicUrl: registration.publicUrl });
129
129
  try {
130
- const configPath = writeFrpcConfig(registration, gatewayPort, "127.0.0.1", "http");
130
+ const configPath = writeFrpcConfig(registration, gatewayPort, "127.0.0.1");
131
131
  this.setStartPhase("starting_frpc", { publicUrl: registration.publicUrl });
132
132
  await this.spawnAndWait(frpcBin, configPath, broker, state, registration.heartbeatIntervalMs);
133
133
  } catch (err) {
@@ -161,6 +161,7 @@ var TunnelService = class extends EventEmitter {
161
161
  let released = false;
162
162
  const persisted = loadTunnelState();
163
163
  const cfg = this.serviceConfig;
164
+ if (persisted) removeFrpcConfig(persisted.tunnelId);
164
165
  if (release && persisted && cfg) {
165
166
  const broker = new TunnelBrokerClient(resolveBrokerApiBase(cfg.brokerUrl));
166
167
  try {
@@ -206,6 +207,7 @@ var TunnelService = class extends EventEmitter {
206
207
  return resumed;
207
208
  }
208
209
  } catch (err) {
210
+ if (!(err instanceof TunnelBrokerError) || err.status !== 410) throw err;
209
211
  log.info({
210
212
  err,
211
213
  tunnelId: persisted.tunnelId,
@@ -218,7 +220,7 @@ var TunnelService = class extends EventEmitter {
218
220
  registrationSecret: cfg.registrationSecret,
219
221
  gatewayVersion: PACKAGE_VERSION,
220
222
  platform: platformLabel(),
221
- gatewayTokenHash: hashGatewayToken(gatewayToken),
223
+ recoveryToken: persisted?.tunnelToken,
222
224
  preferredSubdomain: persisted?.subdomain
223
225
  });
224
226
  }
@@ -285,19 +287,37 @@ var TunnelService = class extends EventEmitter {
285
287
  }
286
288
  }
287
289
  }
290
+ heartbeatGeneration = 0;
288
291
  startHeartbeat(broker, state, intervalMs) {
289
292
  this.clearHeartbeat();
293
+ const generation = this.heartbeatGeneration;
294
+ let pending = false;
290
295
  const tick = async () => {
296
+ if (pending || this.stopping || generation !== this.heartbeatGeneration) return;
297
+ pending = true;
291
298
  try {
292
299
  await broker.heartbeat(state.tunnelId, state.tunnelToken);
293
- this.lastHeartbeatAt = (/* @__PURE__ */ new Date()).toISOString();
300
+ if (generation === this.heartbeatGeneration) this.lastHeartbeatAt = (/* @__PURE__ */ new Date()).toISOString();
294
301
  } catch (err) {
295
302
  log.warn({
296
303
  err,
297
304
  tunnelId: state.tunnelId
298
305
  }, "Tunnel heartbeat failed");
299
- if (this.stopping) return;
306
+ if (this.stopping || generation !== this.heartbeatGeneration) return;
307
+ if (!(err instanceof TunnelBrokerError) || ![
308
+ 401,
309
+ 403,
310
+ 410
311
+ ].includes(err.status)) return;
300
312
  this.clearHeartbeat();
313
+ if (err.status === 401 || err.status === 403) {
314
+ await this.stop();
315
+ this.lastError = "TUNNEL_AUTH_REVOKED";
316
+ this.state = "error";
317
+ this.emit("tunnel:error", this.lastError);
318
+ return;
319
+ }
320
+ const renewalGeneration = this.heartbeatGeneration;
301
321
  const ctx = this.startContext;
302
322
  const cfg = this.serviceConfig;
303
323
  if (!ctx || !cfg) return;
@@ -308,25 +328,32 @@ var TunnelService = class extends EventEmitter {
308
328
  registrationSecret: cfg.registrationSecret,
309
329
  gatewayVersion: PACKAGE_VERSION,
310
330
  platform: platformLabel(),
311
- gatewayTokenHash: hashGatewayToken(ctx.gatewayToken),
331
+ recoveryToken: state.tunnelToken,
312
332
  preferredSubdomain: state.subdomain
313
333
  });
334
+ if (this.stopping || renewalGeneration !== this.heartbeatGeneration) return;
314
335
  const next = persistedFromRegistration(registration);
336
+ removeFrpcConfig(state.tunnelId);
315
337
  saveTunnelState(next);
316
338
  const frpcBin = await ensureFrpcBinary();
317
- const configPath = writeFrpcConfig(registration, ctx.gatewayPort, "127.0.0.1", "http");
339
+ const configPath = writeFrpcConfig(registration, ctx.gatewayPort, "127.0.0.1");
318
340
  await this.spawnAndWait(frpcBin, configPath, broker, next, registration.heartbeatIntervalMs);
319
341
  } catch (reErr) {
342
+ if (this.stopping || renewalGeneration !== this.heartbeatGeneration) return;
343
+ if (reErr instanceof TunnelBrokerError && [401, 403].includes(reErr.status)) await this.stop();
320
344
  this.lastError = reErr instanceof Error ? reErr.message : String(reErr);
321
345
  this.state = "error";
322
346
  this.emit("tunnel:error", this.lastError);
323
347
  }
348
+ } finally {
349
+ pending = false;
324
350
  }
325
351
  };
326
352
  tick();
327
353
  this.heartbeatTimer = setInterval(() => void tick(), intervalMs);
328
354
  }
329
355
  clearHeartbeat() {
356
+ this.heartbeatGeneration += 1;
330
357
  if (this.heartbeatTimer) {
331
358
  clearInterval(this.heartbeatTimer);
332
359
  this.heartbeatTimer = null;
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { join } from "node:path";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
5
  //#region src/tunnel/tunnel-state.ts
6
6
  init_paths();
7
7
  const TUNNEL_STATE_FILE = "tunnel.json";
@@ -22,8 +22,15 @@ function loadTunnelState() {
22
22
  }
23
23
  function saveTunnelState(state) {
24
24
  const path = resolveTunnelStatePath();
25
- mkdirSync(resolveStateDir(), { recursive: true });
26
- writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
25
+ mkdirSync(resolveStateDir(), {
26
+ recursive: true,
27
+ mode: 448
28
+ });
29
+ chmodSync(resolveStateDir(), 448);
30
+ const temporary = `${path}.${process.pid}.tmp`;
31
+ writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 384 });
32
+ chmodSync(temporary, 384);
33
+ renameSync(temporary, path);
27
34
  }
28
35
  function updateTunnelState(patch) {
29
36
  const current = loadTunnelState();
@@ -36,8 +43,7 @@ function updateTunnelState(patch) {
36
43
  return next;
37
44
  }
38
45
  function clearTunnelState() {
39
- const path = resolveTunnelStatePath();
40
- if (existsSync(path)) writeFileSync(path, "{}\n", "utf8");
46
+ rmSync(resolveTunnelStatePath(), { force: true });
41
47
  }
42
48
  //#endregion
43
49
  export { clearTunnelState, loadTunnelState, resolveTunnelStatePath, saveTunnelState, updateTunnelState };
@@ -21,6 +21,7 @@ export type TunnelRegistration = {
21
21
  serverPort: number;
22
22
  authToken: string;
23
23
  proxyName: string;
24
+ subdomain: string;
24
25
  };
25
26
  expiresAt: string;
26
27
  heartbeatIntervalMs: number;
@@ -34,6 +35,7 @@ export type PersistedTunnelState = {
34
35
  registeredAt: string;
35
36
  enabled?: boolean;
36
37
  /** Saved on register so stop/start can resume without re-registering. */
38
+ frpcSubdomain?: string;
37
39
  frpcServerAddr?: string;
38
40
  frpcServerPort?: number;
39
41
  proxyName?: string;