@jingyi0605/codingns4dsh 0.1.0

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 (312) hide show
  1. package/README.md +1114 -0
  2. package/assets/provider-icons/claude-code.png +0 -0
  3. package/assets/provider-icons/codex.png +0 -0
  4. package/assets/provider-icons/command-code.svg +5 -0
  5. package/assets/provider-icons/deepseek-harness.svg +9 -0
  6. package/assets/provider-icons/gemini.png +0 -0
  7. package/assets/provider-icons/grok.png +0 -0
  8. package/assets/provider-icons/kimi.png +0 -0
  9. package/assets/provider-icons/opencode.png +0 -0
  10. package/assets/provider-icons/pi.svg +7 -0
  11. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +97 -0
  12. package/data/build/dist/bootstrap/dsh-connection-adapter.js +169 -0
  13. package/data/build/dist/bootstrap/index.d.ts +38 -0
  14. package/data/build/dist/bootstrap/index.js +44 -0
  15. package/data/build/dist/client/account-bar.d.ts +9 -0
  16. package/data/build/dist/client/account-bar.js +398 -0
  17. package/data/build/dist/client/bundle.js +26167 -0
  18. package/data/build/dist/client/cli-catalog.d.ts +24 -0
  19. package/data/build/dist/client/cli-catalog.js +108 -0
  20. package/data/build/dist/client/cli-slots.d.ts +38 -0
  21. package/data/build/dist/client/cli-slots.js +297 -0
  22. package/data/build/dist/client/debug/ui.d.ts +12 -0
  23. package/data/build/dist/client/debug/ui.js +451 -0
  24. package/data/build/dist/client/dsh-h5-bootstrap.d.ts +64 -0
  25. package/data/build/dist/client/dsh-h5-bootstrap.js +293 -0
  26. package/data/build/dist/client/dsh-runtime-version.d.ts +3 -0
  27. package/data/build/dist/client/dsh-runtime-version.js +11 -0
  28. package/data/build/dist/client/external-tool-stream.d.ts +24 -0
  29. package/data/build/dist/client/external-tool-stream.js +114 -0
  30. package/data/build/dist/client/features/cli-adapters.d.ts +7 -0
  31. package/data/build/dist/client/features/cli-adapters.js +215 -0
  32. package/data/build/dist/client/features/debug.d.ts +4 -0
  33. package/data/build/dist/client/features/debug.js +24 -0
  34. package/data/build/dist/client/features/index.d.ts +33 -0
  35. package/data/build/dist/client/features/index.js +44 -0
  36. package/data/build/dist/client/features/lan-access-panel.d.ts +5 -0
  37. package/data/build/dist/client/features/lan-access-panel.js +146 -0
  38. package/data/build/dist/client/features/lan-access.d.ts +9 -0
  39. package/data/build/dist/client/features/lan-access.js +30 -0
  40. package/data/build/dist/client/features/login-protection-panel.d.ts +5 -0
  41. package/data/build/dist/client/features/login-protection-panel.js +91 -0
  42. package/data/build/dist/client/features/login-protection-session.d.ts +6 -0
  43. package/data/build/dist/client/features/login-protection-session.js +25 -0
  44. package/data/build/dist/client/features/login-protection.d.ts +4 -0
  45. package/data/build/dist/client/features/login-protection.js +23 -0
  46. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +9 -0
  47. package/data/build/dist/client/features/reverse-proxy-panel.js +159 -0
  48. package/data/build/dist/client/features/reverse-proxy.d.ts +12 -0
  49. package/data/build/dist/client/features/reverse-proxy.js +112 -0
  50. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +5 -0
  51. package/data/build/dist/client/features/terminal-enhancement-panel.js +160 -0
  52. package/data/build/dist/client/features/terminal-enhancement.d.ts +4 -0
  53. package/data/build/dist/client/features/terminal-enhancement.js +24 -0
  54. package/data/build/dist/client/features/types.d.ts +58 -0
  55. package/data/build/dist/client/features/types.js +2 -0
  56. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +5 -0
  57. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +66 -0
  58. package/data/build/dist/client/features/workspace-session-enhancement.d.ts +4 -0
  59. package/data/build/dist/client/features/workspace-session-enhancement.js +101 -0
  60. package/data/build/dist/client/h5-bootstrap-entry.d.ts +2 -0
  61. package/data/build/dist/client/h5-bootstrap-entry.js +17 -0
  62. package/data/build/dist/client/index.d.ts +31 -0
  63. package/data/build/dist/client/index.js +98 -0
  64. package/data/build/dist/client/lan-access.d.ts +28 -0
  65. package/data/build/dist/client/lan-access.js +68 -0
  66. package/data/build/dist/client/locale.d.ts +20 -0
  67. package/data/build/dist/client/locale.js +393 -0
  68. package/data/build/dist/client/provider-icon-assets.d.ts +2 -0
  69. package/data/build/dist/client/provider-icon-assets.js +23 -0
  70. package/data/build/dist/client/provider-icons.d.ts +19 -0
  71. package/data/build/dist/client/provider-icons.js +39 -0
  72. package/data/build/dist/client/remote-web-context.d.ts +57 -0
  73. package/data/build/dist/client/remote-web-context.js +939 -0
  74. package/data/build/dist/client/session-adapter-cache.d.ts +18 -0
  75. package/data/build/dist/client/session-adapter-cache.js +84 -0
  76. package/data/build/dist/client/settings-bridge.d.ts +32 -0
  77. package/data/build/dist/client/settings-bridge.js +126 -0
  78. package/data/build/dist/client/settings-section.d.ts +30 -0
  79. package/data/build/dist/client/settings-section.js +93 -0
  80. package/data/build/dist/client/subscription-slot.d.ts +18 -0
  81. package/data/build/dist/client/subscription-slot.js +223 -0
  82. package/data/build/dist/client/terminal/index.d.ts +4 -0
  83. package/data/build/dist/client/terminal/index.js +3 -0
  84. package/data/build/dist/client/terminal/model.d.ts +116 -0
  85. package/data/build/dist/client/terminal/model.js +435 -0
  86. package/data/build/dist/client/terminal/styles.d.ts +21 -0
  87. package/data/build/dist/client/terminal/styles.js +51 -0
  88. package/data/build/dist/client/terminal/ui.d.ts +19 -0
  89. package/data/build/dist/client/terminal/ui.js +257 -0
  90. package/data/build/dist/client/terminal/xterm-view.d.ts +13 -0
  91. package/data/build/dist/client/terminal/xterm-view.js +213 -0
  92. package/data/build/dist/client/theme.d.ts +56 -0
  93. package/data/build/dist/client/theme.js +186 -0
  94. package/data/build/dist/client/workspace-session-archive-dom.d.ts +29 -0
  95. package/data/build/dist/client/workspace-session-archive-dom.js +525 -0
  96. package/data/build/dist/client/workspace-session-fiber.d.ts +3 -0
  97. package/data/build/dist/client/workspace-session-fiber.js +33 -0
  98. package/data/build/dist/client/workspace-session-logo-dom.d.ts +26 -0
  99. package/data/build/dist/client/workspace-session-logo-dom.js +128 -0
  100. package/data/build/dist/features/index.d.ts +3 -0
  101. package/data/build/dist/features/index.js +2 -0
  102. package/data/build/dist/features/registry.d.ts +74 -0
  103. package/data/build/dist/features/registry.js +382 -0
  104. package/data/build/dist/features/resource-scope/index.d.ts +39 -0
  105. package/data/build/dist/features/resource-scope/index.js +161 -0
  106. package/data/build/dist/host/auth-session.d.ts +42 -0
  107. package/data/build/dist/host/auth-session.js +186 -0
  108. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -0
  109. package/data/build/dist/host/cli-adapters/claude-driver.js +103 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +45 -0
  111. package/data/build/dist/host/cli-adapters/codex-driver.js +534 -0
  112. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +38 -0
  113. package/data/build/dist/host/cli-adapters/command-code-driver.js +394 -0
  114. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +19 -0
  115. package/data/build/dist/host/cli-adapters/command-code-subscription.js +126 -0
  116. package/data/build/dist/host/cli-adapters/driver.d.ts +41 -0
  117. package/data/build/dist/host/cli-adapters/driver.js +2 -0
  118. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +37 -0
  119. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +180 -0
  120. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +48 -0
  121. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +373 -0
  122. package/data/build/dist/host/cli-adapters/feature.d.ts +7 -0
  123. package/data/build/dist/host/cli-adapters/feature.js +335 -0
  124. package/data/build/dist/host/cli-adapters/gemini-driver.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/gemini-driver.js +427 -0
  126. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +38 -0
  127. package/data/build/dist/host/cli-adapters/grok-driver.js +372 -0
  128. package/data/build/dist/host/cli-adapters/http-sse-client.d.ts +22 -0
  129. package/data/build/dist/host/cli-adapters/http-sse-client.js +99 -0
  130. package/data/build/dist/host/cli-adapters/interaction-events.d.ts +11 -0
  131. package/data/build/dist/host/cli-adapters/interaction-events.js +80 -0
  132. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +65 -0
  133. package/data/build/dist/host/cli-adapters/json-rpc-process.js +256 -0
  134. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +21 -0
  135. package/data/build/dist/host/cli-adapters/kimi-driver.js +382 -0
  136. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +22 -0
  137. package/data/build/dist/host/cli-adapters/model-catalog.js +89 -0
  138. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +55 -0
  139. package/data/build/dist/host/cli-adapters/opencode-driver.js +728 -0
  140. package/data/build/dist/host/cli-adapters/pi-driver.d.ts +43 -0
  141. package/data/build/dist/host/cli-adapters/pi-driver.js +312 -0
  142. package/data/build/dist/host/cli-adapters/process-utils.d.ts +16 -0
  143. package/data/build/dist/host/cli-adapters/process-utils.js +27 -0
  144. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +82 -0
  145. package/data/build/dist/host/cli-adapters/provider-subscription.js +610 -0
  146. package/data/build/dist/host/cli-adapters/registry.d.ts +95 -0
  147. package/data/build/dist/host/cli-adapters/registry.js +659 -0
  148. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +24 -0
  149. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +119 -0
  150. package/data/build/dist/host/cli-adapters/session-probe.d.ts +20 -0
  151. package/data/build/dist/host/cli-adapters/session-probe.js +139 -0
  152. package/data/build/dist/host/cli-adapters/session-store.d.ts +68 -0
  153. package/data/build/dist/host/cli-adapters/session-store.js +180 -0
  154. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +45 -0
  155. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +205 -0
  156. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +25 -0
  157. package/data/build/dist/host/cli-adapters/stream-normalizer.js +83 -0
  158. package/data/build/dist/host/cli-adapters/tool-observation.d.ts +9 -0
  159. package/data/build/dist/host/cli-adapters/tool-observation.js +39 -0
  160. package/data/build/dist/host/control-api-client.d.ts +84 -0
  161. package/data/build/dist/host/control-api-client.js +130 -0
  162. package/data/build/dist/host/credential-store.d.ts +54 -0
  163. package/data/build/dist/host/credential-store.js +114 -0
  164. package/data/build/dist/host/debug.d.ts +79 -0
  165. package/data/build/dist/host/debug.js +295 -0
  166. package/data/build/dist/host/dsh-device-runtime.d.ts +39 -0
  167. package/data/build/dist/host/dsh-device-runtime.js +118 -0
  168. package/data/build/dist/host/dsh-gateway-feature.d.ts +5 -0
  169. package/data/build/dist/host/dsh-gateway-feature.js +55 -0
  170. package/data/build/dist/host/dsh-runtime-version.d.ts +12 -0
  171. package/data/build/dist/host/dsh-runtime-version.js +69 -0
  172. package/data/build/dist/host/features/auth.d.ts +11 -0
  173. package/data/build/dist/host/features/auth.js +273 -0
  174. package/data/build/dist/host/features/debug.d.ts +5 -0
  175. package/data/build/dist/host/features/debug.js +90 -0
  176. package/data/build/dist/host/features/host-status.d.ts +12 -0
  177. package/data/build/dist/host/features/host-status.js +54 -0
  178. package/data/build/dist/host/features/index.d.ts +19 -0
  179. package/data/build/dist/host/features/index.js +29 -0
  180. package/data/build/dist/host/features/lan-access-dsh.d.ts +8 -0
  181. package/data/build/dist/host/features/lan-access-dsh.js +45 -0
  182. package/data/build/dist/host/features/terminal-process.d.ts +5 -0
  183. package/data/build/dist/host/features/terminal-process.js +110 -0
  184. package/data/build/dist/host/features/terminal-status.d.ts +18 -0
  185. package/data/build/dist/host/features/terminal-status.js +56 -0
  186. package/data/build/dist/host/features/types.d.ts +41 -0
  187. package/data/build/dist/host/features/types.js +2 -0
  188. package/data/build/dist/host/index.d.ts +43 -0
  189. package/data/build/dist/host/index.js +135 -0
  190. package/data/build/dist/host/lan-access-dsh.d.ts +83 -0
  191. package/data/build/dist/host/lan-access-dsh.js +831 -0
  192. package/data/build/dist/host/native-session-bridge.d.ts +130 -0
  193. package/data/build/dist/host/native-session-bridge.js +317 -0
  194. package/data/build/dist/host/relay-tunnel-runtime.d.ts +97 -0
  195. package/data/build/dist/host/relay-tunnel-runtime.js +361 -0
  196. package/data/build/dist/host/remote-web-runtime.d.ts +78 -0
  197. package/data/build/dist/host/remote-web-runtime.js +559 -0
  198. package/data/build/dist/host/rpc-table.d.ts +32 -0
  199. package/data/build/dist/host/rpc-table.js +47 -0
  200. package/data/build/dist/host/rpc.d.ts +18 -0
  201. package/data/build/dist/host/rpc.js +248 -0
  202. package/data/build/dist/host/settings.d.ts +19 -0
  203. package/data/build/dist/host/settings.js +80 -0
  204. package/data/build/dist/host/terminal/attachment-registry.d.ts +17 -0
  205. package/data/build/dist/host/terminal/attachment-registry.js +49 -0
  206. package/data/build/dist/host/terminal/backends/conpty-backend.d.ts +39 -0
  207. package/data/build/dist/host/terminal/backends/conpty-backend.js +160 -0
  208. package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts +36 -0
  209. package/data/build/dist/host/terminal/backends/local-pty-backend.js +178 -0
  210. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -0
  211. package/data/build/dist/host/terminal/backends/tmux-backend.js +191 -0
  212. package/data/build/dist/host/terminal/broker/conpty-broker-client.d.ts +27 -0
  213. package/data/build/dist/host/terminal/broker/conpty-broker-client.js +154 -0
  214. package/data/build/dist/host/terminal/broker/conpty-broker-process.d.ts +10 -0
  215. package/data/build/dist/host/terminal/broker/conpty-broker-process.js +147 -0
  216. package/data/build/dist/host/terminal/broker/conpty-broker-protocol.d.ts +92 -0
  217. package/data/build/dist/host/terminal/broker/conpty-broker-protocol.js +134 -0
  218. package/data/build/dist/host/terminal/controller-factory.d.ts +37 -0
  219. package/data/build/dist/host/terminal/controller-factory.js +82 -0
  220. package/data/build/dist/host/terminal/index.d.ts +17 -0
  221. package/data/build/dist/host/terminal/index.js +17 -0
  222. package/data/build/dist/host/terminal/runtime-adapter.d.ts +77 -0
  223. package/data/build/dist/host/terminal/runtime-adapter.js +18 -0
  224. package/data/build/dist/host/terminal/runtime-manager.d.ts +41 -0
  225. package/data/build/dist/host/terminal/runtime-manager.js +179 -0
  226. package/data/build/dist/host/terminal/shell-detection.d.ts +23 -0
  227. package/data/build/dist/host/terminal/shell-detection.js +105 -0
  228. package/data/build/dist/host/terminal/startup.d.ts +33 -0
  229. package/data/build/dist/host/terminal/startup.js +100 -0
  230. package/data/build/dist/host/terminal/terminal-controller.d.ts +78 -0
  231. package/data/build/dist/host/terminal/terminal-controller.js +324 -0
  232. package/data/build/dist/host/terminal/terminal-process-service.d.ts +30 -0
  233. package/data/build/dist/host/terminal/terminal-process-service.js +194 -0
  234. package/data/build/dist/host/terminal/terminal-process-store.d.ts +45 -0
  235. package/data/build/dist/host/terminal/terminal-process-store.js +183 -0
  236. package/data/build/dist/host/terminal/terminal-service.d.ts +87 -0
  237. package/data/build/dist/host/terminal/terminal-service.js +528 -0
  238. package/data/build/dist/host/terminal/terminal-store.d.ts +49 -0
  239. package/data/build/dist/host/terminal/terminal-store.js +242 -0
  240. package/data/build/dist/index.d.ts +37 -0
  241. package/data/build/dist/index.js +26 -0
  242. package/data/build/dist/shared/contracts/auth.d.ts +122 -0
  243. package/data/build/dist/shared/contracts/auth.js +8 -0
  244. package/data/build/dist/shared/contracts/cli-adapter.d.ts +192 -0
  245. package/data/build/dist/shared/contracts/cli-adapter.js +2 -0
  246. package/data/build/dist/shared/contracts/command-code.d.ts +5 -0
  247. package/data/build/dist/shared/contracts/command-code.js +2 -0
  248. package/data/build/dist/shared/contracts/config.d.ts +113 -0
  249. package/data/build/dist/shared/contracts/config.js +86 -0
  250. package/data/build/dist/shared/contracts/debug-config.d.ts +28 -0
  251. package/data/build/dist/shared/contracts/debug-config.js +78 -0
  252. package/data/build/dist/shared/contracts/debug.d.ts +75 -0
  253. package/data/build/dist/shared/contracts/debug.js +121 -0
  254. package/data/build/dist/shared/contracts/dsh-device.d.ts +72 -0
  255. package/data/build/dist/shared/contracts/dsh-device.js +2 -0
  256. package/data/build/dist/shared/contracts/errors.d.ts +33 -0
  257. package/data/build/dist/shared/contracts/errors.js +41 -0
  258. package/data/build/dist/shared/contracts/feature.d.ts +71 -0
  259. package/data/build/dist/shared/contracts/feature.js +2 -0
  260. package/data/build/dist/shared/contracts/host-status.d.ts +14 -0
  261. package/data/build/dist/shared/contracts/host-status.js +2 -0
  262. package/data/build/dist/shared/contracts/lan-access-dsh.d.ts +30 -0
  263. package/data/build/dist/shared/contracts/lan-access-dsh.js +2 -0
  264. package/data/build/dist/shared/contracts/peer-host.d.ts +37 -0
  265. package/data/build/dist/shared/contracts/peer-host.js +9 -0
  266. package/data/build/dist/shared/contracts/signaling.d.ts +67 -0
  267. package/data/build/dist/shared/contracts/signaling.js +2 -0
  268. package/data/build/dist/shared/contracts/subscription.d.ts +69 -0
  269. package/data/build/dist/shared/contracts/subscription.js +2 -0
  270. package/data/build/dist/shared/contracts/terminal-process.d.ts +65 -0
  271. package/data/build/dist/shared/contracts/terminal-process.js +2 -0
  272. package/data/build/dist/shared/contracts/terminal.d.ts +127 -0
  273. package/data/build/dist/shared/contracts/terminal.js +2 -0
  274. package/data/build/dist/shared/contracts/transport.d.ts +45 -0
  275. package/data/build/dist/shared/contracts/transport.js +3 -0
  276. package/data/build/dist/shared/contracts/version.d.ts +19 -0
  277. package/data/build/dist/shared/contracts/version.js +74 -0
  278. package/data/build/dist/shared/index.d.ts +22 -0
  279. package/data/build/dist/shared/index.js +9 -0
  280. package/data/build/dist/transport/carrier.d.ts +38 -0
  281. package/data/build/dist/transport/carrier.js +268 -0
  282. package/data/build/dist/transport/debug.d.ts +22 -0
  283. package/data/build/dist/transport/debug.js +57 -0
  284. package/data/build/dist/transport/dsh-envelope.d.ts +41 -0
  285. package/data/build/dist/transport/dsh-envelope.js +107 -0
  286. package/data/build/dist/transport/dsh-gateway.d.ts +64 -0
  287. package/data/build/dist/transport/dsh-gateway.js +254 -0
  288. package/data/build/dist/transport/dsh-session.d.ts +55 -0
  289. package/data/build/dist/transport/dsh-session.js +256 -0
  290. package/data/build/dist/transport/dsh-transport.d.ts +57 -0
  291. package/data/build/dist/transport/dsh-transport.js +125 -0
  292. package/data/build/dist/transport/frame.d.ts +132 -0
  293. package/data/build/dist/transport/frame.js +168 -0
  294. package/data/build/dist/transport/index.d.ts +11 -0
  295. package/data/build/dist/transport/index.js +11 -0
  296. package/data/build/dist/transport/multiplexer.d.ts +63 -0
  297. package/data/build/dist/transport/multiplexer.js +323 -0
  298. package/data/build/dist/transport/webrtc-client.d.ts +65 -0
  299. package/data/build/dist/transport/webrtc-client.js +255 -0
  300. package/data/build/dist/transport/webrtc-host.d.ts +58 -0
  301. package/data/build/dist/transport/webrtc-host.js +252 -0
  302. package/data/build/dist/typert.host.d.ts +25 -0
  303. package/data/build/dist/typert.host.js +150 -0
  304. package/dsh.bundle.patch +12 -0
  305. package/package.json +135 -0
  306. package/profile/README.md +27 -0
  307. package/profile/cordis.patch.yml +2 -0
  308. package/profile/package.json +20 -0
  309. package/profile/scripts/check-dsh-install.mjs +71 -0
  310. package/profile/version.json +5 -0
  311. package/scripts/check-dsh-install.mjs +88 -0
  312. package/version.json +6 -0
@@ -0,0 +1,323 @@
1
+ import { decodeDshEnvelope, encodeDshEnvelope } from './dsh-envelope.js';
2
+ import { createDshTransportDebugLogger } from './debug.js';
3
+ const DEFAULT_SCOPE = { hostId: 'unknown', kind: 'remote' };
4
+ /** DSH Envelope 层多路复用器;逻辑 API 保持 rpc/openStream,物理线路只发送二进制 Envelope。 */
5
+ export class DshTunnelMultiplexer {
6
+ carrier;
7
+ pending = new Map();
8
+ streams = new Map();
9
+ sequence = 0;
10
+ nextId = 0;
11
+ disposed = false;
12
+ unsubscribe;
13
+ idPrefix;
14
+ flowControl;
15
+ generation;
16
+ hostScope;
17
+ session;
18
+ requireSessionReady;
19
+ debug;
20
+ constructor(carrier, options = {}) {
21
+ this.carrier = carrier;
22
+ this.idPrefix = options.idPrefix ?? 'g0';
23
+ this.flowControl = options.flowControl;
24
+ this.generation = options.generation ?? this.idPrefix.replace(/^g/u, '');
25
+ this.hostScope = options.hostScope ?? DEFAULT_SCOPE;
26
+ this.session = options.session;
27
+ this.requireSessionReady = options.requireSessionReady ?? options.session !== undefined;
28
+ this.debug = options.debug ?? createDshTransportDebugLogger({ component: 'multiplexer' });
29
+ this.unsubscribe = carrier.subscribe((data) => this.receive(data));
30
+ }
31
+ request(channel, payload, signal) {
32
+ return this.requestOperation(channel, channel === 'fetch' ? 'web.request' : 'rpc.request', payload, signal);
33
+ }
34
+ requestOperation(channel, operation, payload, signal) {
35
+ this.ensureOpen();
36
+ const id = `${this.idPrefix}_req_${this.nextRequestId()}`;
37
+ return new Promise((resolve, reject) => {
38
+ const abort = () => {
39
+ if (!this.pending.delete(id))
40
+ return;
41
+ if (!this.disposed && this.carrier.state === 'open') {
42
+ try {
43
+ this.send({ streamId: id, channel: mapChannel(channel), type: 'stream.cancel', meta: { reason: String(signal?.reason ?? 'cancelled') } });
44
+ }
45
+ catch { /* 取消尽力而为 */ }
46
+ }
47
+ reject(signal?.reason instanceof Error ? signal.reason : new Error('请求已取消'));
48
+ };
49
+ if (signal?.aborted)
50
+ return abort();
51
+ signal?.addEventListener('abort', abort, { once: true });
52
+ this.pending.set(id, {
53
+ resolve: (value) => { signal?.removeEventListener('abort', abort); resolve(value); },
54
+ reject: (error) => { signal?.removeEventListener('abort', abort); reject(error); },
55
+ accepted: false,
56
+ });
57
+ try {
58
+ this.debug.log('request.send', { channel, operation, streamId: id, generation: this.generation, hostId: this.hostScope.hostId, bodyBytes: encodeJson(payload).byteLength });
59
+ this.send({
60
+ streamId: id,
61
+ channel: mapChannel(channel),
62
+ type: 'stream.open',
63
+ meta: { operation, encoding: 'json' },
64
+ body: encodeJson(payload),
65
+ });
66
+ }
67
+ catch (error) {
68
+ this.pending.delete(id);
69
+ signal?.removeEventListener('abort', abort);
70
+ reject(error instanceof Error ? error : new Error(String(error)));
71
+ }
72
+ });
73
+ }
74
+ openStream(payload, signal) {
75
+ return this.openStreamOperation('rpc', 'rpc.stream', payload, signal);
76
+ }
77
+ openStreamOperation(channel, operation, payload, signal) {
78
+ return this.openStreamOperationWithId(channel, operation, payload, signal).stream;
79
+ }
80
+ openStreamOperationWithId(channel, operation, payload, signal) {
81
+ this.ensureOpen();
82
+ if (signal?.aborted)
83
+ return { streamId: '', stream: this.closedStream() };
84
+ const id = `${this.idPrefix}_stream_${this.nextRequestId()}`;
85
+ const state = { queue: [], queuedBytes: 0, waiters: [], closed: false, removeAbort: undefined };
86
+ this.streams.set(id, state);
87
+ const cancel = () => {
88
+ if (state.closed)
89
+ return;
90
+ state.closed = true;
91
+ if (!this.disposed && this.carrier.state === 'open') {
92
+ try {
93
+ this.send({ streamId: id, channel, type: 'stream.cancel', meta: {} });
94
+ }
95
+ catch { /* 关闭路径尽力而为 */ }
96
+ }
97
+ this.finishStream(id, state);
98
+ };
99
+ if (signal) {
100
+ signal.addEventListener('abort', cancel, { once: true });
101
+ state.removeAbort = () => signal.removeEventListener('abort', cancel);
102
+ }
103
+ try {
104
+ this.send({ streamId: id, channel, type: 'stream.open', meta: { operation, encoding: 'json' }, body: encodeJson(payload) });
105
+ }
106
+ catch (error) {
107
+ state.error = error instanceof Error ? error : new Error(String(error));
108
+ this.finishStream(id, state);
109
+ }
110
+ const stream = { [Symbol.asyncIterator]: () => ({
111
+ next: async () => {
112
+ if (state.error)
113
+ throw state.error;
114
+ if (state.queue.length > 0) {
115
+ state.queuedBytes = 0;
116
+ return { done: false, value: state.queue.shift() };
117
+ }
118
+ if (state.closed)
119
+ return { done: true, value: undefined };
120
+ return new Promise((resolve, reject) => state.waiters.push({ resolve: (result) => resolve({ done: result.done === true, value: result.value }), reject }));
121
+ },
122
+ return: async () => { cancel(); return { done: true, value: undefined }; },
123
+ }) };
124
+ return { streamId: id, stream };
125
+ }
126
+ sendStreamMessage(streamId, channel, type, meta = {}, body) {
127
+ this.ensureOpen();
128
+ this.send({ streamId, channel, type, meta, ...(body === undefined ? {} : { body }) });
129
+ }
130
+ closeStream(streamId, channel = 'web') {
131
+ if (this.disposed || this.carrier.state !== 'open')
132
+ return;
133
+ try {
134
+ this.sendStreamMessage(streamId, channel, 'stream.cancel', {});
135
+ }
136
+ catch { /* 关闭路径尽力而为 */ }
137
+ }
138
+ close(error = new Error('Transport 已关闭')) {
139
+ if (this.disposed)
140
+ return;
141
+ this.disposed = true;
142
+ this.unsubscribe();
143
+ for (const pending of this.pending.values())
144
+ pending.reject(error);
145
+ this.pending.clear();
146
+ for (const [id, stream] of this.streams) {
147
+ stream.error = error;
148
+ this.finishStream(id, stream);
149
+ }
150
+ }
151
+ invalidate(error = new Error('Transport generation 已过期')) {
152
+ for (const pending of this.pending.values())
153
+ pending.reject(error);
154
+ this.pending.clear();
155
+ for (const [id, stream] of this.streams) {
156
+ stream.error = error;
157
+ this.finishStream(id, stream);
158
+ }
159
+ }
160
+ /** 替换物理 carrier;逻辑请求已在 invalidate 中失败,后续请求使用新线路。 */
161
+ replaceCarrier(carrier) {
162
+ if (this.disposed)
163
+ throw new Error('Transport 已关闭');
164
+ this.unsubscribe();
165
+ this.carrier = carrier;
166
+ this.unsubscribe = carrier.subscribe((data) => this.receive(data));
167
+ }
168
+ setSession(session) {
169
+ this.session = session;
170
+ }
171
+ rotateGeneration(idPrefix, error = new Error('Transport generation 已过期')) {
172
+ this.invalidate(error);
173
+ this.idPrefix = idPrefix;
174
+ this.generation = idPrefix.replace(/^g/u, '');
175
+ }
176
+ receive(data) {
177
+ let envelope;
178
+ try {
179
+ if (typeof data === 'string') {
180
+ const legacy = JSON.parse(data);
181
+ envelope = legacyEnvelope(legacy);
182
+ }
183
+ else {
184
+ const options = this.flowControl?.maxFrameBytes === undefined ? {} : { maxBytes: this.flowControl.maxFrameBytes };
185
+ envelope = decodeDshEnvelope(data, options);
186
+ }
187
+ }
188
+ catch (error) {
189
+ this.debug.log('envelope.decode.error', { bytes: typeof data === 'string' ? data.length : data.byteLength, error: error instanceof Error ? error.message : String(error) });
190
+ this.close(error instanceof Error ? error : new Error(String(error)));
191
+ return;
192
+ }
193
+ this.debug.log('envelope.receive', envelopeDebugFields(envelope, typeof data === 'string' ? new TextEncoder().encode(data).byteLength : data.byteLength));
194
+ if (envelope.generation !== this.generation || envelope.hostScope.hostId !== this.hostScope.hostId || envelope.hostScope.kind !== this.hostScope.kind) {
195
+ this.debug.log('envelope.scope.drop', { expectedGeneration: this.generation, expectedHostId: this.hostScope.hostId, expectedHostKind: this.hostScope.kind, ...envelopeDebugFields(envelope, undefined) });
196
+ return;
197
+ }
198
+ this.flowControl?.onReceive?.(typeof data === 'string' ? new TextEncoder().encode(data).byteLength : data.byteLength, envelope);
199
+ const state = this.streams.get(envelope.streamId);
200
+ const pending = this.pending.get(envelope.streamId);
201
+ if (state)
202
+ return this.receiveStream(envelope, state);
203
+ if (!pending)
204
+ return;
205
+ if (envelope.type === 'stream.accepted') {
206
+ pending.accepted = true;
207
+ return;
208
+ }
209
+ if (envelope.type.endsWith('.response')) {
210
+ this.pending.delete(envelope.streamId);
211
+ pending.resolve(decodePayload(envelope));
212
+ }
213
+ else if (envelope.type === 'stream.close') {
214
+ this.pending.delete(envelope.streamId);
215
+ pending.resolve(decodePayload(envelope));
216
+ }
217
+ else if (envelope.type === 'stream.error' || envelope.type.endsWith('.error')) {
218
+ this.pending.delete(envelope.streamId);
219
+ pending.reject(new Error(typeof envelope.meta.detail === 'string' ? envelope.meta.detail : '远端请求失败'));
220
+ }
221
+ }
222
+ receiveStream(envelope, state) {
223
+ if (envelope.type === 'stream.accepted')
224
+ return;
225
+ if (envelope.type === 'stream.error' || envelope.type.endsWith('.error')) {
226
+ state.error = new Error(typeof envelope.meta.detail === 'string' ? envelope.meta.detail : '远端流失败');
227
+ this.finishStream(envelope.streamId, state);
228
+ return;
229
+ }
230
+ if (envelope.type === 'stream.close' || envelope.flags?.endOfStream) {
231
+ this.finishStream(envelope.streamId, state);
232
+ return;
233
+ }
234
+ if (envelope.type === 'stream.window')
235
+ return;
236
+ const value = decodePayload(envelope);
237
+ state.queuedBytes += envelope.body?.byteLength ?? 0;
238
+ if (state.queuedBytes > (this.flowControl?.maxQueueBytes ?? 4 * 1024 * 1024)) {
239
+ state.error = new Error('Transport 队列超过上限');
240
+ this.finishStream(envelope.streamId, state);
241
+ return;
242
+ }
243
+ if (state.waiters.length > 0)
244
+ state.waiters.shift()?.resolve({ done: false, value });
245
+ else
246
+ state.queue.push(value);
247
+ }
248
+ finishStream(id, state) {
249
+ state.closed = true;
250
+ state.removeAbort?.();
251
+ state.removeAbort = undefined;
252
+ this.streams.delete(id);
253
+ for (const waiter of state.waiters.splice(0))
254
+ state.error ? waiter.reject(state.error) : waiter.resolve({ done: true, value: undefined });
255
+ }
256
+ send(input) {
257
+ const envelope = { version: 1, messageId: `${this.idPrefix}_m_${this.nextSequence()}`, streamId: input.streamId, channel: input.channel, type: input.type, sequence: this.nextSequence(), generation: this.generation, hostScope: this.hostScope, meta: input.meta ?? {}, ...(input.body === undefined ? {} : { body: input.body }), ...(input.flags === undefined ? {} : { flags: input.flags }) };
258
+ const encoded = encodeDshEnvelope(envelope, this.flowControl?.maxFrameBytes === undefined ? {} : { maxBytes: this.flowControl.maxFrameBytes });
259
+ if (this.flowControl?.canSend && !this.flowControl.canSend(encoded.byteLength, envelope))
260
+ throw new Error('Transport 背压窗口不足');
261
+ const pending = this.carrier.send(encoded);
262
+ this.debug.log('envelope.send', envelopeDebugFields(envelope, encoded.byteLength));
263
+ if (pending && typeof pending.catch === 'function') {
264
+ void pending.catch((error) => this.close(error instanceof Error ? error : new Error(String(error))));
265
+ }
266
+ this.flowControl?.onSend?.(encoded.byteLength, envelope);
267
+ }
268
+ nextSequence() { if (this.sequence >= Number.MAX_SAFE_INTEGER)
269
+ throw new Error('DSH Envelope sequence 已耗尽'); return ++this.sequence; }
270
+ nextRequestId() { if (this.nextId >= Number.MAX_SAFE_INTEGER)
271
+ throw new Error('Transport request id 已耗尽'); return ++this.nextId; }
272
+ closedStream(error) { return { [Symbol.asyncIterator]: () => ({ next: async () => error ? Promise.reject(error) : ({ done: true, value: undefined }) }) }; }
273
+ ensureOpen() { if (this.disposed || this.carrier.state !== 'open')
274
+ throw new Error('Transport 尚未 ready'); if (this.requireSessionReady && !this.session?.ready)
275
+ throw new Error('SESSION_NOT_READY'); }
276
+ }
277
+ function envelopeDebugFields(envelope, bytes) {
278
+ return {
279
+ type: envelope.type,
280
+ channel: envelope.channel,
281
+ streamId: envelope.streamId,
282
+ sequence: envelope.sequence,
283
+ generation: envelope.generation,
284
+ hostId: envelope.hostScope.hostId,
285
+ hostKind: envelope.hostScope.kind,
286
+ operation: typeof envelope.meta.operation === 'string' ? envelope.meta.operation : undefined,
287
+ bodyBytes: envelope.body?.byteLength ?? 0,
288
+ ...(bytes === undefined ? {} : { frameBytes: bytes }),
289
+ };
290
+ }
291
+ function mapChannel(channel) { return channel === 'fetch' || channel === 'web' ? 'web' : channel === 'control' ? 'session' : 'rpc'; }
292
+ function encodeJson(value) { return new TextEncoder().encode(JSON.stringify(value === undefined ? null : value)); }
293
+ function decodePayload(envelope) {
294
+ if (envelope.meta.encoding === 'json' && envelope.body) {
295
+ try {
296
+ return JSON.parse(new TextDecoder().decode(envelope.body));
297
+ }
298
+ catch {
299
+ return envelope.body;
300
+ }
301
+ }
302
+ if (envelope.meta.encoding === 'text' && envelope.body)
303
+ return new TextDecoder().decode(envelope.body);
304
+ return envelope.body ?? envelope.meta.payload;
305
+ }
306
+ function legacyEnvelope(value) {
307
+ if (typeof value.id !== 'string' || typeof value.kind !== 'string' || !Number.isSafeInteger(value.sequence))
308
+ throw new Error('Tunnel Frame 无效');
309
+ const channel = value.channel === 'fetch' ? 'web' : value.channel === 'control' ? 'session' : 'rpc';
310
+ return {
311
+ version: 1,
312
+ messageId: value.id,
313
+ streamId: value.id,
314
+ channel,
315
+ type: `${value.channel}.${value.kind}`,
316
+ sequence: value.sequence,
317
+ generation: '1',
318
+ hostScope: { hostId: 'unknown', kind: 'remote' },
319
+ meta: { encoding: 'json', ...(value.payload === undefined ? {} : { payload: value.payload }) },
320
+ ...(value.payload === undefined ? {} : { body: new TextEncoder().encode(JSON.stringify(value.payload)) }),
321
+ };
322
+ }
323
+ //# sourceMappingURL=multiplexer.js.map
@@ -0,0 +1,65 @@
1
+ import type { RelayIceServer, RelaySignalingTicketResponse } from '../shared/index.js';
2
+ import { type CodingNsCarrier, type DataChannelLike } from './carrier.js';
3
+ import { type DshTransportDebugLogger } from './debug.js';
4
+ export interface SignalingSocketLike {
5
+ send(data: string): void;
6
+ close(code?: number, reason?: string): void;
7
+ addEventListener(type: 'message' | 'close' | 'error', listener: (event: Event) => void): void;
8
+ removeEventListener(type: 'message' | 'close' | 'error', listener: (event: Event) => void): void;
9
+ }
10
+ export interface PeerConnectionLike {
11
+ createDataChannel(label: string): DataChannelLike;
12
+ createOffer(): Promise<{
13
+ type: 'offer';
14
+ sdp?: string;
15
+ }>;
16
+ setLocalDescription(description: {
17
+ type: string;
18
+ sdp?: string;
19
+ }): Promise<void>;
20
+ setRemoteDescription(description: {
21
+ type: string;
22
+ sdp: string;
23
+ }): Promise<void>;
24
+ addIceCandidate(candidate: {
25
+ candidate: string;
26
+ sdpMid: string | null;
27
+ }): Promise<void>;
28
+ close(): void;
29
+ onicecandidate: ((event: {
30
+ candidate: {
31
+ candidate: string;
32
+ sdpMid: string | null;
33
+ } | null;
34
+ }) => void) | null;
35
+ addEventListener?(type: string, listener: (event: Event) => void): void;
36
+ removeEventListener?(type: string, listener: (event: Event) => void): void;
37
+ }
38
+ export interface WebRtcClientConnectorOptions {
39
+ signalingTicket: RelaySignalingTicketResponse;
40
+ signalingSocketFactory(url: string): Promise<SignalingSocketLike> | SignalingSocketLike;
41
+ peerConnectionFactory(options: {
42
+ iceServers: RelayIceServer[];
43
+ iceTransportPolicy: 'all' | 'relay';
44
+ }): PeerConnectionLike;
45
+ channelLabel?: string;
46
+ timeoutMs?: number;
47
+ heartbeatIntervalMs?: number;
48
+ debug?: DshTransportDebugLogger;
49
+ }
50
+ export interface WebRtcClientConnection {
51
+ carrier: CodingNsCarrier;
52
+ peerConnection: PeerConnectionLike;
53
+ signaling: SignalingSocketLike;
54
+ onClosed(listener: (error?: Error) => void): () => void;
55
+ close(): Promise<void>;
56
+ }
57
+ /** 客户端发起 offer,校验 Host fingerprint 后返回 DataChannel Carrier。 */
58
+ export declare function connectWebRtcClient(options: WebRtcClientConnectorOptions): Promise<WebRtcClientConnection>;
59
+ export declare function createSignalingUrl(baseUrl: string, ticket: string): string;
60
+ export declare function waitForSignalingRegistered(socket: SignalingSocketLike, timeoutMs: number, cleanup?: Array<() => void>): Promise<void>;
61
+ /** Relay 只有在 Host 在线后才接受客户端 offer,避免过早发送被 HOST_NOT_CONNECTED 拒绝。 */
62
+ export declare function waitForPeerReady(socket: SignalingSocketLike, timeoutMs: number, cleanup?: Array<() => void>): Promise<void>;
63
+ export declare function extractDtlsFingerprint(sdp: string): string | null;
64
+ export declare function assertDtlsFingerprint(expected: string, sdp: string): void;
65
+ //# sourceMappingURL=webrtc-client.d.ts.map
@@ -0,0 +1,255 @@
1
+ import { createDataChannelCarrier, TUNNEL_DATA_CHANNEL_LABEL } from './carrier.js';
2
+ import { encodeFrame } from './frame.js';
3
+ import { createDshTransportDebugLogger } from './debug.js';
4
+ /** 客户端发起 offer,校验 Host fingerprint 后返回 DataChannel Carrier。 */
5
+ export async function connectWebRtcClient(options) {
6
+ const ticket = options.signalingTicket;
7
+ const debug = options.debug ?? createDshTransportDebugLogger({ side: 'h5', component: 'webrtc-client' });
8
+ const signalingUrl = createSignalingUrl(ticket.signalingBaseUrl, ticket.ticket);
9
+ debug.log('signaling.connect', { signalingBaseUrl: ticket.signalingBaseUrl });
10
+ const signaling = await options.signalingSocketFactory(signalingUrl);
11
+ const cleanupListeners = [];
12
+ // 真实 WebSocket 暴露 readyState;测试注入的最小 socket 可能没有该字段。
13
+ // 生产路径严格等待 registered,旧的纯协议 fake 仍可直接进入 offer 流程。
14
+ if ('readyState' in signaling) {
15
+ await waitForSignalingRegistered(signaling, options.timeoutMs ?? 15_000, cleanupListeners);
16
+ await waitForPeerReady(signaling, options.timeoutMs ?? 15_000, cleanupListeners);
17
+ }
18
+ const heartbeat = options.heartbeatIntervalMs === 0 ? null : setInterval(() => {
19
+ try {
20
+ signaling.send(JSON.stringify({ type: 'ping', at: new Date().toISOString() }));
21
+ }
22
+ catch { /* 断线由 close 事件处理 */ }
23
+ }, options.heartbeatIntervalMs ?? 20_000);
24
+ cleanupListeners.push(() => { if (heartbeat)
25
+ clearInterval(heartbeat); });
26
+ const peerConnection = options.peerConnectionFactory({
27
+ iceServers: ticket.iceServers,
28
+ iceTransportPolicy: ticket.iceTransportPolicy,
29
+ });
30
+ // Relay Tunnel 的标签属于线协议,不能由调用方改写。
31
+ const channel = peerConnection.createDataChannel(TUNNEL_DATA_CHANNEL_LABEL);
32
+ const carrier = createDataChannelCarrier(channel, { ...(options.debug ? { debug: options.debug } : {}) });
33
+ let closed = false;
34
+ let closeNotified = false;
35
+ const closeListeners = new Set();
36
+ const notifyClosed = (error) => {
37
+ if (closeNotified)
38
+ return;
39
+ closeNotified = true;
40
+ debug.log('webrtc.connection.closed', { reason: error?.message ?? 'closed' });
41
+ for (const listener of [...closeListeners])
42
+ listener(error);
43
+ closeListeners.clear();
44
+ };
45
+ const onSignalingClose = () => notifyClosed(new Error('Relay signaling closed'));
46
+ const onSignalingError = () => notifyClosed(new Error('Relay signaling error'));
47
+ const onPeerState = () => {
48
+ const state = peerConnection;
49
+ const value = state.connectionState ?? state.iceConnectionState;
50
+ debug.log('webrtc.peer.state', { state: value ?? 'unknown' });
51
+ if (value === 'failed' || value === 'disconnected' || value === 'closed')
52
+ notifyClosed(new Error(`PeerConnection ${value}`));
53
+ };
54
+ signaling.addEventListener('close', onSignalingClose);
55
+ signaling.addEventListener('error', onSignalingError);
56
+ peerConnection.addEventListener?.('connectionstatechange', onPeerState);
57
+ peerConnection.addEventListener?.('iceconnectionstatechange', onPeerState);
58
+ const carrierClosed = (reason) => notifyClosed(new Error(reason ?? 'DataChannel closed'));
59
+ carrier.onClosed?.(carrierClosed);
60
+ const close = async () => {
61
+ if (closed)
62
+ return;
63
+ closed = true;
64
+ notifyClosed(new Error('client closed'));
65
+ signaling.removeEventListener('close', onSignalingClose);
66
+ signaling.removeEventListener('error', onSignalingError);
67
+ peerConnection.removeEventListener?.('connectionstatechange', onPeerState);
68
+ peerConnection.removeEventListener?.('iceconnectionstatechange', onPeerState);
69
+ for (const cleanup of cleanupListeners.splice(0))
70
+ cleanup();
71
+ await carrier.close();
72
+ peerConnection.close();
73
+ signaling.close(1000, 'client closed');
74
+ };
75
+ try {
76
+ const answerPromise = waitForAnswer(signaling, options.timeoutMs ?? 15_000, cleanupListeners);
77
+ peerConnection.onicecandidate = (event) => {
78
+ if (!event.candidate)
79
+ return;
80
+ signaling.send(JSON.stringify({
81
+ type: 'candidate',
82
+ candidate: event.candidate.candidate,
83
+ mid: event.candidate.sdpMid,
84
+ }));
85
+ };
86
+ const offer = await peerConnection.createOffer();
87
+ await peerConnection.setLocalDescription(offer);
88
+ signaling.send(JSON.stringify({ type: 'offer', sdp: offer.sdp ?? '' }));
89
+ debug.log('signaling.offer.sent', { sdpBytes: offer.sdp?.length ?? 0 });
90
+ const answer = await answerPromise;
91
+ assertDtlsFingerprint(ticket.hostDtlsFingerprint, answer.sdp);
92
+ await peerConnection.setRemoteDescription({ type: 'answer', sdp: answer.sdp });
93
+ for (const candidate of answer.candidates)
94
+ await peerConnection.addIceCandidate(candidate);
95
+ await waitForOpen(channel, options.timeoutMs ?? 15_000, cleanupListeners);
96
+ await carrier.send(encodeFrame({ type: 'hello', clientContext: null, protocolVersion: '1' }));
97
+ debug.log('webrtc.connected', { channelLabel: TUNNEL_DATA_CHANNEL_LABEL });
98
+ return { carrier, peerConnection, signaling, onClosed: (listener) => { if (closeNotified) {
99
+ listener(new Error('connection closed'));
100
+ return () => undefined;
101
+ } closeListeners.add(listener); return () => closeListeners.delete(listener); }, close };
102
+ }
103
+ catch (error) {
104
+ await close();
105
+ throw error;
106
+ }
107
+ }
108
+ export function createSignalingUrl(baseUrl, ticket) {
109
+ const base = new URL(ensureWebSocketProtocol(baseUrl));
110
+ const pathname = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`;
111
+ const url = new URL('signal', `${base.origin}${pathname}`);
112
+ url.searchParams.set('ticket', ticket);
113
+ return url.toString();
114
+ }
115
+ export function waitForSignalingRegistered(socket, timeoutMs, cleanup = []) {
116
+ return new Promise((resolve, reject) => {
117
+ const timer = setTimeout(() => { remove(); reject(new Error('等待 Relay registered 超时')); }, timeoutMs);
118
+ const onMessage = (event) => {
119
+ const raw = event.data;
120
+ if (typeof raw !== 'string')
121
+ return;
122
+ try {
123
+ const message = JSON.parse(raw);
124
+ if (message.type === 'error') {
125
+ clearTimeout(timer);
126
+ remove();
127
+ const code = message.errorCode?.trim() || 'RELAY_ERROR';
128
+ const detail = message.detail?.trim();
129
+ reject(new Error(detail ? `Relay ${code}: ${detail}` : `Relay ${code}`));
130
+ return;
131
+ }
132
+ if (message.type !== 'registered')
133
+ return;
134
+ clearTimeout(timer);
135
+ remove();
136
+ resolve();
137
+ }
138
+ catch { /* 忽略非 JSON 信令 */ }
139
+ };
140
+ const onClose = () => { clearTimeout(timer); remove(); reject(new Error('信令连接在 registered 前关闭')); };
141
+ const remove = () => { socket.removeEventListener('message', onMessage); socket.removeEventListener('close', onClose); };
142
+ socket.addEventListener('message', onMessage);
143
+ socket.addEventListener('close', onClose);
144
+ cleanup.push(remove);
145
+ });
146
+ }
147
+ /** Relay 只有在 Host 在线后才接受客户端 offer,避免过早发送被 HOST_NOT_CONNECTED 拒绝。 */
148
+ export function waitForPeerReady(socket, timeoutMs, cleanup = []) {
149
+ return new Promise((resolve, reject) => {
150
+ const timer = setTimeout(() => { remove(); reject(new Error('等待 Relay peer-ready 超时')); }, timeoutMs);
151
+ const onMessage = (event) => {
152
+ const raw = event.data;
153
+ if (typeof raw !== 'string')
154
+ return;
155
+ try {
156
+ const message = JSON.parse(raw);
157
+ if (message.type === 'peer-ready') {
158
+ clearTimeout(timer);
159
+ remove();
160
+ resolve();
161
+ }
162
+ else if (message.type === 'error') {
163
+ clearTimeout(timer);
164
+ remove();
165
+ reject(new Error('Relay 拒绝 peer-ready'));
166
+ }
167
+ }
168
+ catch { /* 忽略非 JSON 信令 */ }
169
+ };
170
+ const onClose = () => { clearTimeout(timer); remove(); reject(new Error('信令连接在 peer-ready 前关闭')); };
171
+ const remove = () => { socket.removeEventListener('message', onMessage); socket.removeEventListener('close', onClose); };
172
+ socket.addEventListener('message', onMessage);
173
+ socket.addEventListener('close', onClose);
174
+ cleanup.push(remove);
175
+ });
176
+ }
177
+ export function extractDtlsFingerprint(sdp) {
178
+ const match = sdp.match(/^a=fingerprint:([^\r\n]+)$/mu);
179
+ return match?.[1] ? normalizeFingerprint(match[1]) : null;
180
+ }
181
+ export function assertDtlsFingerprint(expected, sdp) {
182
+ const actual = extractDtlsFingerprint(sdp);
183
+ if (!actual || actual !== normalizeFingerprint(expected)) {
184
+ throw new Error('Host DTLS fingerprint 校验失败');
185
+ }
186
+ }
187
+ function waitForAnswer(socket, timeoutMs, cleanup) {
188
+ return new Promise((resolve, reject) => {
189
+ const timer = setTimeout(() => reject(new Error('等待 Host answer 超时')), timeoutMs);
190
+ const candidates = [];
191
+ const onMessage = (event) => {
192
+ const raw = event.data;
193
+ if (typeof raw !== 'string')
194
+ return;
195
+ let message;
196
+ try {
197
+ message = JSON.parse(raw);
198
+ }
199
+ catch {
200
+ return;
201
+ }
202
+ if (message.type === 'answer') {
203
+ clearTimeout(timer);
204
+ remove();
205
+ resolve({ sdp: message.sdp, candidates });
206
+ }
207
+ else if (message.type === 'candidate') {
208
+ candidates.push({ candidate: message.candidate, sdpMid: message.mid });
209
+ }
210
+ else if (message.type === 'error') {
211
+ clearTimeout(timer);
212
+ remove();
213
+ reject(new Error(`${message.errorCode}: ${message.detail}`));
214
+ }
215
+ };
216
+ const onClose = () => { clearTimeout(timer); remove(); reject(new Error('信令连接已关闭')); };
217
+ const remove = () => {
218
+ socket.removeEventListener('message', onMessage);
219
+ socket.removeEventListener('close', onClose);
220
+ };
221
+ socket.addEventListener('message', onMessage);
222
+ socket.addEventListener('close', onClose);
223
+ cleanup.push(remove);
224
+ });
225
+ }
226
+ function waitForOpen(channel, timeoutMs, cleanup) {
227
+ if (channel.readyState === 'open')
228
+ return Promise.resolve();
229
+ return new Promise((resolve, reject) => {
230
+ const timer = setTimeout(() => { remove(); reject(new Error('等待 DataChannel open 超时')); }, timeoutMs);
231
+ const onOpen = () => { clearTimeout(timer); remove(); resolve(); };
232
+ const onClose = () => { clearTimeout(timer); remove(); reject(new Error('DataChannel 在 ready 前关闭')); };
233
+ const remove = () => {
234
+ channel.removeEventListener('open', onOpen);
235
+ channel.removeEventListener('close', onClose);
236
+ };
237
+ channel.addEventListener('open', onOpen);
238
+ channel.addEventListener('close', onClose);
239
+ cleanup.push(remove);
240
+ });
241
+ }
242
+ function normalizeFingerprint(value) {
243
+ return value.trim().replace(/^[a-z0-9-]+(?:\s+|:)+/iu, '').replace(/[^0-9a-f]/giu, '').toLowerCase();
244
+ }
245
+ function ensureWebSocketProtocol(value) {
246
+ const url = new URL(value);
247
+ if (url.protocol === 'http:')
248
+ url.protocol = 'ws:';
249
+ if (url.protocol === 'https:')
250
+ url.protocol = 'wss:';
251
+ if (url.protocol !== 'ws:' && url.protocol !== 'wss:')
252
+ throw new Error('Relay URL 必须使用 HTTP(S) 或 WS(S)');
253
+ return url.toString();
254
+ }
255
+ //# sourceMappingURL=webrtc-client.js.map