@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,939 @@
1
+ import { createDshTransportDebugLogger } from '../transport/debug.js';
2
+ /**
3
+ * 一个 HostScope 对应一个独立 iframe。iframe 内的 fetch 和 WebSocket 都经由
4
+ * postMessage 回到父页面,再由 DSH Transport 走 web.* Envelope;页面本身不接触
5
+ * ticket、Control API 凭据或 Host 的本地地址。
6
+ */
7
+ export class RemoteDshWebContext {
8
+ options;
9
+ objectUrls = new Set();
10
+ moduleUrls = new Map();
11
+ moduleLoads = new Map();
12
+ sockets = new Map();
13
+ /** 请求可能来自不同生命周期的 iframe;响应必须回到原发送窗口。 */
14
+ messageWindows = new Map();
15
+ socketWindows = new Map();
16
+ iframeWindows = new Set();
17
+ /** WebSocket 只有在 Host 本地 socket 真正打开后才允许 iframe 发送首帧。 */
18
+ socketReady = new Map();
19
+ iframeValue;
20
+ sessionIdValue;
21
+ disposed = false;
22
+ onMessageBound = (event) => { void this.onMessage(event); };
23
+ debug;
24
+ constructor(options) {
25
+ this.options = options;
26
+ if (typeof document === 'undefined')
27
+ throw new Error('Remote DSH Web Context 只能运行在浏览器');
28
+ this.debug = options.debug ?? createDshTransportDebugLogger({ side: 'h5', component: 'remote-web-bridge' });
29
+ }
30
+ get iframe() { return this.iframeValue; }
31
+ get sessionId() { return this.sessionIdValue; }
32
+ async open(signal) {
33
+ this.ensureOpen();
34
+ window.addEventListener('message', this.onMessageBound);
35
+ window.__CODINGNS4DSH_REMOTE_TRANSPORT__ = this.options.transport;
36
+ const session = await this.options.transport.webRequest('web.session.open', {
37
+ ...(this.options.workspaceId ? { workspaceId: this.options.workspaceId } : {}),
38
+ ...(this.options.sessionId ? { sessionId: this.options.sessionId } : {}),
39
+ }, signal);
40
+ this.sessionIdValue = session.sessionId;
41
+ const boot = await this.options.transport.webRequest('web.boot.get', { sessionId: session.sessionId }, signal);
42
+ const iframe = document.createElement('iframe');
43
+ iframe.className = 'dsh-remote-web-context';
44
+ iframe.setAttribute('title', 'Remote DSH Web');
45
+ // Blob URL 由父页面创建;allow-same-origin 让 srcdoc 与父页面共享 origin,
46
+ // 否则浏览器会把 blob:https://dsh.codingns.com/... 视为跨 origin 本地资源并拒绝加载。
47
+ iframe.setAttribute('sandbox', 'allow-downloads allow-forms allow-modals allow-popups allow-scripts allow-same-origin');
48
+ iframe.setAttribute('referrerpolicy', 'no-referrer');
49
+ iframe.style.width = '100%';
50
+ iframe.style.height = '100%';
51
+ iframe.style.border = '0';
52
+ iframe.srcdoc = await this.prepareBootHtml(boot, signal);
53
+ this.options.container.replaceChildren(iframe);
54
+ this.iframeValue = iframe;
55
+ if (iframe.contentWindow !== null)
56
+ this.iframeWindows.add(iframe.contentWindow);
57
+ }
58
+ async dispose() {
59
+ if (this.disposed)
60
+ return;
61
+ this.disposed = true;
62
+ window.removeEventListener('message', this.onMessageBound);
63
+ for (const streamId of this.sockets.values())
64
+ this.options.transport.closeWebStream(streamId);
65
+ this.sockets.clear();
66
+ this.messageWindows.clear();
67
+ this.socketWindows.clear();
68
+ this.iframeWindows.clear();
69
+ for (const waiter of this.socketReady.values())
70
+ waiter.reject(new Error('Remote DSH Web Context 已关闭'));
71
+ this.socketReady.clear();
72
+ if (this.sessionIdValue !== undefined) {
73
+ try {
74
+ await this.options.transport.webRequest('web.session.close', { sessionId: this.sessionIdValue });
75
+ }
76
+ catch {
77
+ // 物理连接已断开时,Host 会随 generation 一并清理会话。
78
+ }
79
+ }
80
+ for (const url of this.objectUrls)
81
+ URL.revokeObjectURL(url);
82
+ this.objectUrls.clear();
83
+ this.iframeValue?.remove();
84
+ this.iframeValue = undefined;
85
+ const transportWindow = window;
86
+ if (transportWindow.__CODINGNS4DSH_REMOTE_TRANSPORT__ === this.options.transport)
87
+ delete transportWindow.__CODINGNS4DSH_REMOTE_TRANSPORT__;
88
+ this.sessionIdValue = undefined;
89
+ }
90
+ async prepareBootHtml(boot, signal) {
91
+ const parser = new DOMParser();
92
+ const documentValue = parser.parseFromString(boot.html, 'text/html');
93
+ relaxRemoteContentSecurityPolicy(documentValue);
94
+ // srcdoc 在 sandbox 中没有可访问的网络 origin;固定一个不可路由的基址,
95
+ // 让 DSH Web 生成的相对 URL 仍能被 bridge 归一化为路径。
96
+ const base = documentValue.createElement('base');
97
+ base.href = 'https://dsh.remote.invalid/';
98
+ documentValue.head.prepend(base);
99
+ const scriptNodes = [...documentValue.querySelectorAll('script[src]')];
100
+ const inlineScriptNodes = [...documentValue.querySelectorAll('script:not([src])')];
101
+ const styleNodes = [...documentValue.querySelectorAll('link[rel="stylesheet"][href]')];
102
+ // modulepreload、manifest 和 favicon 指向的是不可访问的 Host 地址;模块依赖
103
+ // 会由 loadScript 重写为 Blob URL,其他链接直接移除,避免浏览器绕过 Tunnel。
104
+ for (const node of [...documentValue.querySelectorAll('link')]) {
105
+ const rel = (node.getAttribute('rel') ?? '').toLowerCase();
106
+ const as = (node.getAttribute('as') ?? '').toLowerCase();
107
+ if (rel === 'modulepreload' || rel === 'manifest' || rel === 'icon' || (rel === 'preload' && as === 'script'))
108
+ node.remove();
109
+ }
110
+ await Promise.all([
111
+ ...scriptNodes.map(async (node) => {
112
+ node.removeAttribute('integrity');
113
+ node.removeAttribute('crossorigin');
114
+ const path = resolveRemotePath(node.getAttribute('src') ?? '');
115
+ node.src = await this.loadScript(path, signal);
116
+ }),
117
+ ...inlineScriptNodes.map(async (node) => {
118
+ const type = (node.getAttribute('type') ?? '').toLowerCase();
119
+ // JSON 数据脚本不是可执行代码,保留给 DSH 前端读取;其余内联脚本
120
+ // 转成 Blob 外链,以兼容 Bootstrap 的 script-src 无 unsafe-inline 策略。
121
+ if (type === 'application/json' || type === 'application/ld+json')
122
+ return;
123
+ const source = node.textContent ?? '';
124
+ node.textContent = '';
125
+ node.src = this.createObjectUrl(new TextEncoder().encode(source), 'text/javascript');
126
+ }),
127
+ ...styleNodes.map(async (node) => {
128
+ node.removeAttribute('integrity');
129
+ node.removeAttribute('crossorigin');
130
+ const path = resolveRemotePath(node.getAttribute('href') ?? '');
131
+ node.href = await this.loadStyle(path, signal);
132
+ }),
133
+ ]);
134
+ const bridge = documentValue.createElement('script');
135
+ bridge.src = this.createObjectUrl(new TextEncoder().encode(createBridgeScript()), 'text/javascript');
136
+ documentValue.head.prepend(bridge);
137
+ return `<!doctype html>${documentValue.documentElement.outerHTML}`;
138
+ }
139
+ async loadScript(path, signal) {
140
+ const cached = this.moduleUrls.get(path);
141
+ if (cached !== undefined)
142
+ return cached;
143
+ const pending = this.moduleLoads.get(path);
144
+ if (pending !== undefined)
145
+ return pending;
146
+ const load = (async () => {
147
+ const body = await this.options.transport.webRequest('web.asset.get', { sessionId: this.sessionIdValue, path }, signal);
148
+ let source = decodeText(body);
149
+ // DSH client-modules 的 `require.async("./client.foo.js")` 是包内懒加载
150
+ // 标记,必须保留给 loader 依据 manifest 生成带 ownerId/rev 的 URL;
151
+ // 如果按聚合 URL 直接归一化,会错误变成 `/plugins/client.foo.js`。
152
+ const references = collectRelativeReferences(source, /\.(?:js)(?:\?[^\s"'`)]*)?$/u)
153
+ .filter((reference) => !(path.startsWith('/plugins/??') && /^\.\/client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js(?:\?.*)?$/u.test(reference)));
154
+ const replacements = await Promise.all(references.map(async (reference) => {
155
+ const dependencyPath = resolveRelativeAssetPath(path, reference);
156
+ return [reference, await this.loadScript(dependencyPath, signal)];
157
+ }));
158
+ for (const [reference, url] of replacements)
159
+ source = source.split(reference).join(url);
160
+ const url = this.createObjectUrl(new TextEncoder().encode(source), 'text/javascript');
161
+ this.moduleUrls.set(path, url);
162
+ return url;
163
+ })();
164
+ this.moduleLoads.set(path, load);
165
+ return load;
166
+ }
167
+ async loadStyle(path, signal) {
168
+ const cached = this.moduleUrls.get(path);
169
+ if (cached !== undefined)
170
+ return cached;
171
+ const body = await this.options.transport.webRequest('web.asset.get', { sessionId: this.sessionIdValue, path }, signal);
172
+ let source = decodeText(body);
173
+ const references = collectCssReferences(source, /\.(?:woff2?|ttf|otf|png|svg)(?:\?[^\s"'`)]*)?$/u);
174
+ const replacements = await Promise.all(references.map(async (reference) => {
175
+ const dependencyPath = resolveRelativeAssetPath(path, reference);
176
+ const dependency = await this.options.transport.webRequest('web.asset.get', { sessionId: this.sessionIdValue, path: dependencyPath }, signal);
177
+ return [reference, this.createObjectUrl(dependency, contentTypeForPath(dependencyPath))];
178
+ }));
179
+ for (const [reference, url] of replacements)
180
+ source = source.split(reference).join(url);
181
+ const url = this.createObjectUrl(new TextEncoder().encode(source), 'text/css');
182
+ this.moduleUrls.set(path, url);
183
+ return url;
184
+ }
185
+ createObjectUrl(value, contentType) {
186
+ if (!(value instanceof Uint8Array))
187
+ throw new Error('Remote DSH Web 资源必须是二进制');
188
+ const copy = new Uint8Array(value.byteLength);
189
+ copy.set(value);
190
+ const url = URL.createObjectURL(new Blob([copy.buffer], { type: contentType }));
191
+ this.objectUrls.add(url);
192
+ return url;
193
+ }
194
+ async onMessage(event) {
195
+ const sourceWindow = event.source;
196
+ if (this.disposed || sourceWindow === null || !this.iframeWindows.has(sourceWindow))
197
+ return;
198
+ if (!isRecord(event.data) || typeof event.data.kind !== 'string')
199
+ return;
200
+ const message = event.data;
201
+ if (message.kind !== 'dsh-web-debug' && typeof message.id !== 'string')
202
+ return;
203
+ if (typeof message.id === 'string')
204
+ this.messageWindows.set(message.id, sourceWindow);
205
+ if (message.kind !== 'dsh-web-debug') {
206
+ this.debug.log('bridge.message.accepted', { kind: message.kind, id: message.id });
207
+ }
208
+ try {
209
+ if (message.kind === 'dsh-web-debug') {
210
+ const eventName = typeof message.event === 'string' ? message.event : 'unknown';
211
+ const fields = isRecord(message.fields) ? message.fields : {};
212
+ this.debug.log(`iframe.${eventName}`, fields);
213
+ if (/^(bridge\.ws\.|client\.connection\.)/u.test(eventName)) {
214
+ await this.options.transport.webRequest('web.debug', { event: eventName, fields });
215
+ }
216
+ return;
217
+ }
218
+ if (message.kind === 'fetch') {
219
+ const input = isRecord(message.input) ? message.input : {};
220
+ const path = resolveRemotePath(typeof input.path === 'string' ? input.path : '/');
221
+ const body = typeof message.body === 'string'
222
+ ? message.body
223
+ : typeof input.body === 'string'
224
+ ? input.body
225
+ : undefined;
226
+ const response = path.startsWith('/assets/') || path.startsWith('/plugins/')
227
+ ? { status: 200, headers: [['content-type', 'application/octet-stream']], body: await this.options.transport.webRequest('web.asset.get', { sessionId: this.sessionIdValue, path }) }
228
+ : await this.options.transport.webRequest('web.request', { sessionId: this.sessionIdValue, path, method: typeof input.method === 'string' ? input.method : 'GET', ...(Array.isArray(input.headers) ? { headers: input.headers } : {}), ...(body === undefined ? {} : { body }) });
229
+ this.postResponse(message.id, { ok: true, status: response.status, headers: response.headers, body: response.body instanceof Uint8Array ? response.body.buffer : response.body });
230
+ return;
231
+ }
232
+ if (message.kind === 'script') {
233
+ const input = isRecord(message.input) ? message.input : {};
234
+ const path = resolveRemotePath(typeof input.path === 'string' ? input.path : '/');
235
+ const url = await this.loadScript(path, undefined);
236
+ this.postResponse(message.id, { ok: true, url });
237
+ return;
238
+ }
239
+ if (message.kind === 'style') {
240
+ const input = isRecord(message.input) ? message.input : {};
241
+ const path = resolveRemotePath(typeof input.path === 'string' ? input.path : '/');
242
+ const url = await this.loadStyle(path, undefined);
243
+ this.postResponse(message.id, { ok: true, url });
244
+ return;
245
+ }
246
+ if (message.kind === 'ws.open') {
247
+ const input = isRecord(message.input) ? message.input : {};
248
+ const opened = this.options.transport.openWebStreamWithId('web.ws.open', { sessionId: this.sessionIdValue, path: resolveRemotePath(typeof input.path === 'string' ? input.path : '/') });
249
+ this.sockets.set(message.id, opened.streamId);
250
+ this.socketWindows.set(message.id, sourceWindow);
251
+ this.debug.log('bridge.ws.register', { id: message.id, streamId: opened.streamId });
252
+ let resolveReady;
253
+ let rejectReady;
254
+ const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
255
+ this.socketReady.set(message.id, { resolve: resolveReady, reject: rejectReady });
256
+ this.debug.log('bridge.ws.open', { id: message.id, streamId: opened.streamId, path: resolveRemotePath(typeof input.path === 'string' ? input.path : '/') });
257
+ void this.consumeSocket(message.id, opened.streamId, opened.stream);
258
+ // 不再把 stream.open/stream.accepted 当作 WebSocket open。只有 Host
259
+ // 已完成本地 WebSocket 握手并发回 web.ws.open.response,iframe 才能
260
+ // 发送 /api/remote.mux 的首个 DSH Connection 帧。
261
+ await ready;
262
+ if (!this.disposed && this.sockets.get(message.id) === opened.streamId) {
263
+ this.debug.log('bridge.ws.open.response', { id: message.id, streamId: opened.streamId });
264
+ this.postResponse(message.id, { ok: true });
265
+ }
266
+ return;
267
+ }
268
+ if (message.kind === 'ws.send') {
269
+ const streamId = this.sockets.get(message.id);
270
+ this.debug.log('bridge.ws.receive', { id: message.id, hasSocket: streamId !== undefined, streamId: streamId ?? null });
271
+ if (!streamId)
272
+ throw new Error('Remote DSH WebSocket 不存在');
273
+ const body = typeof message.body === 'string' ? new TextEncoder().encode(message.body) : toBytes(message.body);
274
+ this.debug.log('bridge.ws.send', { id: message.id, streamId, bytes: body.byteLength, encoding: typeof message.body === 'string' ? 'text' : 'binary' });
275
+ try {
276
+ this.options.transport.sendWebStream(streamId, 'web.ws.data', body, { ...(typeof message.body === 'string' ? { encoding: 'text' } : { binary: true }) });
277
+ this.debug.log('bridge.ws.data.sent', { id: message.id, streamId, bytes: body.byteLength });
278
+ }
279
+ catch (error) {
280
+ this.debug.log('bridge.ws.data.error', { id: message.id, streamId, error: error instanceof Error ? error.message : String(error) });
281
+ throw error;
282
+ }
283
+ return;
284
+ }
285
+ if (message.kind === 'ws.close') {
286
+ const streamId = this.sockets.get(message.id);
287
+ if (streamId)
288
+ this.options.transport.closeWebStream(streamId);
289
+ this.debug.log('bridge.ws.close', { id: message.id, streamId });
290
+ this.sockets.delete(message.id);
291
+ this.socketWindows.delete(message.id);
292
+ }
293
+ }
294
+ catch (error) {
295
+ this.postResponse(message.id, { ok: false, error: error instanceof Error ? error.message : 'Remote DSH Web 请求失败' });
296
+ }
297
+ }
298
+ async consumeSocket(id, streamId, stream) {
299
+ let opened = false;
300
+ try {
301
+ for await (const value of stream) {
302
+ if (!opened) {
303
+ opened = true;
304
+ if (isRecord(value) && value.opened === true) {
305
+ this.socketReady.get(id)?.resolve();
306
+ this.socketReady.delete(id);
307
+ continue;
308
+ }
309
+ }
310
+ if (value instanceof Uint8Array) {
311
+ this.debug.log('bridge.ws.message', { id, streamId, bytes: value.byteLength, encoding: 'binary' });
312
+ this.postEvent(id, 'message', value.buffer);
313
+ }
314
+ else {
315
+ const body = typeof value === 'string' ? value : JSON.stringify(value);
316
+ this.debug.log('bridge.ws.message', { id, streamId, bytes: new TextEncoder().encode(body).byteLength, encoding: 'text' });
317
+ this.postEvent(id, 'message', body);
318
+ }
319
+ }
320
+ this.postEvent(id, 'close', undefined);
321
+ }
322
+ catch (error) {
323
+ const failure = error instanceof Error ? error : new Error('Remote DSH WebSocket 流失败');
324
+ this.socketReady.get(id)?.reject(failure);
325
+ this.socketReady.delete(id);
326
+ this.postEvent(id, 'error', error instanceof Error ? error.message : 'Remote DSH WebSocket 流失败');
327
+ }
328
+ finally {
329
+ if (!opened) {
330
+ const failure = new Error('Remote DSH WebSocket 未收到 Host open 响应');
331
+ this.socketReady.get(id)?.reject(failure);
332
+ this.socketReady.delete(id);
333
+ }
334
+ if (this.sockets.get(id) === streamId)
335
+ this.sockets.delete(id);
336
+ this.socketWindows.delete(id);
337
+ this.debug.log('bridge.ws.consume.finally', { id, streamId, hasSocket: this.sockets.has(id) });
338
+ }
339
+ }
340
+ postResponse(id, value) {
341
+ this.messageWindows.get(id)?.postMessage({ kind: 'dsh-web-response', id, ...value }, '*');
342
+ }
343
+ postEvent(id, type, body) {
344
+ this.socketWindows.get(id)?.postMessage({ kind: 'dsh-web-event', id, type, body }, '*');
345
+ }
346
+ ensureOpen() { if (this.disposed)
347
+ throw new Error('Remote DSH Web Context 已关闭'); }
348
+ }
349
+ function createBridgeScript() {
350
+ return `(() => {
351
+ // 远程 DSH Web 已经运行在外层 DSH-Codingns4DSH Tunnel 内。
352
+ // 内嵌的 codingns4dsh Client 仍需加载其插件代码和界面,但不能再次启动
353
+ // 自己的 Relay/WebRTC,否则会把信令 WebSocket 当成本地 DSH Web 路径转发,
354
+ // 形成递归连接并持续触发 /signaling/signal 失败。
355
+ globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ = true;
356
+ const bridgeDebugEnabled = (() => {
357
+ try {
358
+ const query = new URL(parent.location.href).searchParams.get('dshDebug');
359
+ return /^(1|true|yes|on)$/iu.test(query || '');
360
+ } catch { return false; }
361
+ })();
362
+ const bridgeLog = (event, fields = {}) => {
363
+ if (!bridgeDebugEnabled) return;
364
+ const payload = { at: new Date().toISOString(), side: 'h5', component: 'remote-web-bridge', event, ...fields };
365
+ console.info('[codingns4dsh:tunnel]', payload);
366
+ parent.postMessage({ kind: 'dsh-web-debug', event, fields: payload }, '*');
367
+ };
368
+ const pending = new Map();
369
+ // RPC、WebSocket 和远程 stream 必须使用各自的 ID 空间;资源 fetch
370
+ // 不能改变 WebSocket 的 ID,否则 ws.open 与 ws.send 会指向不同 key。
371
+ let nextCallId = 0;
372
+ let nextSocketId = 0;
373
+ let nextStreamId = 0;
374
+ let remoteLoadChain = Promise.resolve();
375
+ const call = (kind, input, body, explicitId) => new Promise((resolve, reject) => {
376
+ const id = explicitId || String(++nextCallId);
377
+ pending.set(id, { resolve, reject });
378
+ bridgeLog('bridge.call', { kind, id, path: input && typeof input.path === 'string' ? input.path : undefined });
379
+ parent.postMessage({ kind, id, input, body }, '*');
380
+ });
381
+ // srcdoc 的 location.href 是 about:srcdoc,不能作为相对 URL 的基址。
382
+ // prepareBootHtml 已写入固定 base,所有资源解析必须以 document.baseURI 为准。
383
+ const resourceBase = () => document.baseURI && document.baseURI !== 'about:srcdoc' ? document.baseURI : 'https://dsh.remote.invalid/';
384
+ const isRemoteResource = (value) => {
385
+ try {
386
+ const parsed = new URL(value, resourceBase());
387
+ return parsed.protocol !== 'blob:' && (parsed.origin === location.origin || parsed.origin === 'null' || parsed.origin === 'https://dsh.remote.invalid');
388
+ } catch {
389
+ return false;
390
+ }
391
+ };
392
+ const remotePath = (value) => {
393
+ const parsed = new URL(value, resourceBase());
394
+ return parsed.pathname + parsed.search;
395
+ };
396
+ const resourceAttribute = (node, attribute) => node && (node.getAttribute('data-dsh-bridge-source') || node.getAttribute(attribute) || node[attribute] || '');
397
+ const isRemoteScript = (node) => node && node.nodeType === 1 && node.tagName === 'SCRIPT' && node.getAttribute('data-dsh-bridge-loaded') !== '1' && isRemoteResource(resourceAttribute(node, 'src'));
398
+ const isRemoteStyle = (node) => node && node.nodeType === 1 && node.tagName === 'LINK' && (node.getAttribute('rel') || '').toLowerCase() === 'stylesheet' && node.getAttribute('data-dsh-bridge-loaded') !== '1' && isRemoteResource(resourceAttribute(node, 'href'));
399
+ const queueRemoteNode = (parentNode, node, beforeNode, kind, attribute) => {
400
+ const source = resourceAttribute(node, attribute);
401
+ const onload = node.onload;
402
+ const onerror = node.onerror;
403
+ remoteLoadChain = remoteLoadChain.then(async () => {
404
+ const response = await call(kind, { path: remotePath(source) });
405
+ if (!response || typeof response.url !== 'string') throw new Error('远程资源加载失败');
406
+ // 原地复用节点,保留 loader 通过 addEventListener 注册的 load/error 监听器。
407
+ // cloneNode 只复制属性,不复制监听器,会让 client-modules 永远等待。
408
+ node.removeAttribute('data-dsh-bridge-source');
409
+ node.setAttribute('data-dsh-bridge-loaded', '1');
410
+ node.setAttribute(attribute, response.url);
411
+ if (beforeNode) nativeInsertBefore.call(parentNode, node, beforeNode);
412
+ else nativeAppendChild.call(parentNode, node);
413
+ }).catch((error) => {
414
+ console.error('[codingns4dsh] remote resource load failed', error);
415
+ try {
416
+ if (typeof onerror === 'function') onerror.call(node, error);
417
+ else node.dispatchEvent(new Event('error'));
418
+ } catch {}
419
+ });
420
+ return node;
421
+ };
422
+ const nativeAppendChild = Node.prototype.appendChild;
423
+ const nativeInsertBefore = Node.prototype.insertBefore;
424
+ const nativeRemoveChild = Node.prototype.removeChild;
425
+ const nativeAppend = Element.prototype.append;
426
+ const nativePrepend = Element.prototype.prepend;
427
+ const nativeFragmentAppend = DocumentFragment.prototype.append;
428
+ const nativeFragmentPrepend = DocumentFragment.prototype.prepend;
429
+ const nativeInsertAdjacentHTML = Element.prototype.insertAdjacentHTML;
430
+ const nativeReplaceChildren = Element.prototype.replaceChildren;
431
+ const nativeDocumentWrite = Document.prototype.write;
432
+ const nativeDocumentWriteln = Document.prototype.writeln;
433
+ const scriptSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src');
434
+ const linkHrefDescriptor = Object.getOwnPropertyDescriptor(HTMLLinkElement.prototype, 'href');
435
+ if (scriptSrcDescriptor?.set && scriptSrcDescriptor.get) {
436
+ Object.defineProperty(HTMLScriptElement.prototype, 'src', {
437
+ configurable: true,
438
+ get() { return this.getAttribute('data-dsh-bridge-source') || scriptSrcDescriptor.get.call(this); },
439
+ set(value) {
440
+ const source = String(value);
441
+ if (isRemoteResource(source)) {
442
+ this.setAttribute('data-dsh-bridge-source', source);
443
+ scriptSrcDescriptor.set.call(this, 'about:blank');
444
+ } else scriptSrcDescriptor.set.call(this, value);
445
+ },
446
+ });
447
+ }
448
+ if (linkHrefDescriptor?.set && linkHrefDescriptor.get) {
449
+ Object.defineProperty(HTMLLinkElement.prototype, 'href', {
450
+ configurable: true,
451
+ get() { return this.getAttribute('data-dsh-bridge-source') || linkHrefDescriptor.get.call(this); },
452
+ set(value) {
453
+ const source = String(value);
454
+ if (isRemoteResource(source)) {
455
+ this.setAttribute('data-dsh-bridge-source', source);
456
+ linkHrefDescriptor.set.call(this, 'about:blank');
457
+ } else linkHrefDescriptor.set.call(this, value);
458
+ },
459
+ });
460
+ }
461
+ const queueFragmentResources = (parentNode, fragment) => {
462
+ if (!fragment || fragment.nodeType !== 11 || !fragment.querySelectorAll) return false;
463
+ const resources = [...fragment.querySelectorAll('script[src],link[rel="stylesheet"][href]')].filter((node) => isRemoteScript(node) || isRemoteStyle(node));
464
+ if (resources.length === 0) return false;
465
+ for (const node of resources) {
466
+ const resourceParent = node.parentNode;
467
+ if (!resourceParent) continue;
468
+ nativeRemoveChild.call(resourceParent, node);
469
+ queueRemoteNode(parentNode, node, null, isRemoteScript(node) ? 'script' : 'style', isRemoteScript(node) ? 'src' : 'href');
470
+ }
471
+ return true;
472
+ };
473
+ Node.prototype.appendChild = function(node) {
474
+ if (isRemoteScript(node)) return queueRemoteNode(this, node, null, 'script', 'src');
475
+ if (isRemoteStyle(node)) return queueRemoteNode(this, node, null, 'style', 'href');
476
+ return nativeAppendChild.call(this, node);
477
+ };
478
+ Node.prototype.insertBefore = function(node, beforeNode) {
479
+ if (isRemoteScript(node)) return queueRemoteNode(this, node, beforeNode, 'script', 'src');
480
+ if (isRemoteStyle(node)) return queueRemoteNode(this, node, beforeNode, 'style', 'href');
481
+ return nativeInsertBefore.call(this, node, beforeNode);
482
+ };
483
+ Element.prototype.append = function(...nodes) {
484
+ for (const node of nodes) {
485
+ if (isRemoteScript(node)) queueRemoteNode(this, node, null, 'script', 'src');
486
+ else if (isRemoteStyle(node)) queueRemoteNode(this, node, null, 'style', 'href');
487
+ else if (queueFragmentResources(this, node)) nativeAppend.call(this, node);
488
+ else if (typeof node === 'string' && /<(?:script|link)\b/iu.test(node)) {
489
+ const template = document.createElement('template');
490
+ template.innerHTML = node;
491
+ for (const child of [...template.content.childNodes]) {
492
+ if (isRemoteScript(child)) queueRemoteNode(this, child, null, 'script', 'src');
493
+ else if (isRemoteStyle(child)) queueRemoteNode(this, child, null, 'style', 'href');
494
+ else nativeAppendChild.call(this, child);
495
+ }
496
+ }
497
+ else nativeAppend.call(this, node);
498
+ }
499
+ };
500
+ Element.prototype.prepend = function(...nodes) {
501
+ for (const node of [...nodes].reverse()) {
502
+ if (isRemoteScript(node)) queueRemoteNode(this, node, this.firstChild, 'script', 'src');
503
+ else if (isRemoteStyle(node)) queueRemoteNode(this, node, this.firstChild, 'style', 'href');
504
+ else if (queueFragmentResources(this, node)) nativePrepend.call(this, node);
505
+ else if (typeof node === 'string' && /<(?:script|link)\b/iu.test(node)) {
506
+ const template = document.createElement('template');
507
+ template.innerHTML = node;
508
+ for (const child of [...template.content.childNodes].reverse()) {
509
+ if (isRemoteScript(child)) queueRemoteNode(this, child, this.firstChild, 'script', 'src');
510
+ else if (isRemoteStyle(child)) queueRemoteNode(this, child, this.firstChild, 'style', 'href');
511
+ else nativeInsertBefore.call(this, child, this.firstChild);
512
+ }
513
+ }
514
+ else nativePrepend.call(this, node);
515
+ }
516
+ };
517
+ // 部分插件 loader 先把 script 放进 DocumentFragment,再一次性挂到 head。
518
+ // DocumentFragment 不继承 Element.prototype,这条路径必须单独接管。
519
+ DocumentFragment.prototype.append = function(...nodes) {
520
+ for (const node of nodes) {
521
+ if (isRemoteScript(node)) queueRemoteNode(this, node, null, 'script', 'src');
522
+ else if (isRemoteStyle(node)) queueRemoteNode(this, node, null, 'style', 'href');
523
+ else if (queueFragmentResources(this, node)) nativeFragmentAppend.call(this, node);
524
+ else nativeFragmentAppend.call(this, node);
525
+ }
526
+ };
527
+ DocumentFragment.prototype.prepend = function(...nodes) {
528
+ for (const node of [...nodes].reverse()) {
529
+ if (isRemoteScript(node)) queueRemoteNode(this, node, this.firstChild, 'script', 'src');
530
+ else if (isRemoteStyle(node)) queueRemoteNode(this, node, this.firstChild, 'style', 'href');
531
+ else if (queueFragmentResources(this, node)) nativeFragmentPrepend.call(this, node);
532
+ else nativeFragmentPrepend.call(this, node);
533
+ }
534
+ };
535
+ Element.prototype.replaceChildren = function(...nodes) {
536
+ const remoteNodes = nodes.filter((node) => isRemoteScript(node) || isRemoteStyle(node));
537
+ nativeReplaceChildren.call(this, ...nodes.filter((node) => !isRemoteScript(node) && !isRemoteStyle(node)));
538
+ for (const node of remoteNodes) queueRemoteNode(this, node, null, isRemoteScript(node) ? 'script' : 'style', isRemoteScript(node) ? 'src' : 'href');
539
+ };
540
+ Element.prototype.insertAdjacentHTML = function(position, html) {
541
+ if (typeof html !== 'string' || !/(?:<script\b|<link\b)/iu.test(html)) {
542
+ return nativeInsertAdjacentHTML.call(this, position, html);
543
+ }
544
+ const template = document.createElement('template');
545
+ template.innerHTML = html;
546
+ const target = position === 'beforebegin' || position === 'afterend' ? this.parentNode : this;
547
+ if (!target) return nativeInsertAdjacentHTML.call(this, position, html);
548
+ const children = [...template.content.childNodes];
549
+ const ordered = position === 'afterbegin' ? children.reverse() : children;
550
+ for (const child of ordered) {
551
+ if (isRemoteScript(child)) {
552
+ queueRemoteNode(target, child, position === 'beforebegin' ? this : position === 'afterbegin' ? this.firstChild : null, 'script', 'src');
553
+ } else if (isRemoteStyle(child)) {
554
+ queueRemoteNode(target, child, position === 'beforebegin' ? this : position === 'afterbegin' ? this.firstChild : null, 'style', 'href');
555
+ } else if (position === 'beforebegin') nativeInsertBefore.call(target, child, this);
556
+ else if (position === 'afterend') nativeInsertBefore.call(target, child, this.nextSibling);
557
+ else if (position === 'afterbegin') nativeInsertBefore.call(target, child, this.firstChild);
558
+ else nativeAppendChild.call(target, child);
559
+ }
560
+ };
561
+ const writeRemoteMarkup = (documentNode, html) => {
562
+ if (typeof html !== 'string' || !/(?:<script\b|<link\b)/iu.test(html)) return false;
563
+ const template = documentNode.createElement('template');
564
+ template.innerHTML = html;
565
+ const parent = documentNode.head || documentNode.documentElement;
566
+ for (const child of [...template.content.childNodes]) {
567
+ if (isRemoteScript(child)) queueRemoteNode(parent, child, null, 'script', 'src');
568
+ else if (isRemoteStyle(child)) queueRemoteNode(parent, child, null, 'style', 'href');
569
+ else nativeAppendChild.call(parent, child);
570
+ }
571
+ return true;
572
+ };
573
+ Document.prototype.write = function(html) {
574
+ if (!writeRemoteMarkup(this, html)) nativeDocumentWrite.call(this, html);
575
+ };
576
+ Document.prototype.writeln = function(html) {
577
+ if (!writeRemoteMarkup(this, html)) nativeDocumentWriteln.call(this, html);
578
+ };
579
+ const handleAddedNode = (node) => {
580
+ if (!node || node.nodeType !== 1) return;
581
+ if (isRemoteScript(node) || isRemoteStyle(node)) {
582
+ const parentNode = node.parentNode;
583
+ if (!parentNode) return;
584
+ nativeRemoveChild.call(parentNode, node);
585
+ queueRemoteNode(parentNode, node, null, isRemoteScript(node) ? 'script' : 'style', isRemoteScript(node) ? 'src' : 'href');
586
+ }
587
+ if (node.querySelectorAll) for (const child of node.querySelectorAll('script[src],link[rel="stylesheet"][href]')) handleAddedNode(child);
588
+ };
589
+ new MutationObserver((records) => {
590
+ for (const record of records) for (const node of record.addedNodes) handleAddedNode(node);
591
+ }).observe(document, { childList: true, subtree: true });
592
+ // 远程模式的 DSH Web 使用 memory persistence,欢迎声明在每次 iframe
593
+ // 重建时都会重新出现。通过点击 DSH 自己的确认按钮关闭它,既能让
594
+ // DSH 完成状态更新,也能触发 OnboardingModal 恢复 #root.inert。
595
+ const welcomeTitles = new Set(['内测声明', 'Welcome Notice', 'Beta Notice', 'Internal Testing Notice']);
596
+ const welcomeButtons = new Set(['继续', 'Continue']);
597
+ const normalizeText = (value) => String(value || '').replace(/\s+/gu, ' ').trim();
598
+ const welcomeTitle = (root) => normalizeText(root.querySelector('h1,h2,h3,[role="heading"],[data-testid="modal-title"]')?.textContent || root.getAttribute('aria-label'));
599
+ const welcomeRoots = () => {
600
+ const roots = new Set(document.querySelectorAll('[role="dialog"],dialog,[class*="onboardingOverlay"]'));
601
+ // 兼容没有 role/aria-modal 的旧版 Modal:从标题向上找到包含按钮的最近容器。
602
+ for (const heading of document.querySelectorAll('h1,h2,h3,[role="heading"]')) {
603
+ if (!welcomeTitles.has(normalizeText(heading.textContent))) continue;
604
+ let root = heading;
605
+ for (let depth = 0; depth < 8 && root; depth += 1, root = root.parentElement) {
606
+ if (root.querySelector('button')) {
607
+ roots.add(root);
608
+ break;
609
+ }
610
+ }
611
+ }
612
+ return roots;
613
+ };
614
+ const hideWelcomeRoot = (root) => {
615
+ root.setAttribute('hidden', '');
616
+ root.setAttribute('aria-hidden', 'true');
617
+ if (root instanceof HTMLElement) root.style.display = 'none';
618
+ // CSS 隐藏不会触发 DSH OnboardingModal 的 cleanup,必须同步解除 inert。
619
+ const appRoot = document.getElementById('root');
620
+ if (appRoot) appRoot.inert = false;
621
+ };
622
+ const acknowledgeRemoteWelcome = () => {
623
+ for (const root of welcomeRoots()) {
624
+ if (!welcomeTitles.has(welcomeTitle(root))) continue;
625
+ const button = [...root.querySelectorAll('button')].find((candidate) => !candidate.disabled && welcomeButtons.has(normalizeText(candidate.textContent)));
626
+ if (!button || button.dataset.dshCodingnsAutoAcknowledged === '1') {
627
+ if (!button) hideWelcomeRoot(root);
628
+ continue;
629
+ }
630
+ button.dataset.dshCodingnsAutoAcknowledged = '1';
631
+ // 等待当前 React 提交完成后再触发事件,确保 onClick 已经绑定。
632
+ queueMicrotask(() => {
633
+ if (button.isConnected && !button.disabled) button.click();
634
+ setTimeout(() => {
635
+ // 某些构建把 React 事件委托绑定放在 effect 中,首个 click 可能早于委托注册;
636
+ // 若弹窗仍在 DOM 中,补一次点击,仍未关闭则直接隐藏并解除 inert。
637
+ if (button.isConnected && !button.disabled) button.click();
638
+ if (root.isConnected) hideWelcomeRoot(root);
639
+ }, 0);
640
+ });
641
+ }
642
+ };
643
+ acknowledgeRemoteWelcome();
644
+ new MutationObserver(acknowledgeRemoteWelcome).observe(document, { childList: true, subtree: true });
645
+ addEventListener('message', (event) => {
646
+ const value = event.data;
647
+ if (!value || value.kind === undefined) return;
648
+ if (value.kind === 'dsh-web-response') {
649
+ const item = pending.get(value.id);
650
+ if (!item) return;
651
+ pending.delete(value.id);
652
+ if (value.ok === false) item.reject(new Error(value.error || 'Remote DSH Web 请求失败'));
653
+ else item.resolve(value);
654
+ }
655
+ if (value.kind === 'dsh-web-event') {
656
+ const item = pending.get(value.id);
657
+ if (item && value.type === 'error') item.reject(new Error(value.body || 'Remote DSH WebSocket 失败'));
658
+ const target = window.__dshRemoteSockets && window.__dshRemoteSockets.get(value.id);
659
+ if (target) {
660
+ if (value.type === 'message') target._emit?.('message', { data: value.body });
661
+ if (value.type === 'close') {
662
+ target.readyState = 3;
663
+ target._emit?.('close', new CloseEvent('close'));
664
+ }
665
+ if (value.type === 'error') target._emit?.('error', new Event('error'));
666
+ }
667
+ }
668
+ });
669
+ const originalFetch = window.fetch.bind(window);
670
+ window.fetch = async (input, init) => {
671
+ // DSH client-connection 使用 URL 对象调用 fetch;Request 则提供 url。
672
+ // 两者都必须保留原始路径,否则会把请求错误地转成 /undefined。
673
+ const url = typeof input === 'string'
674
+ ? input
675
+ : input instanceof URL
676
+ ? input.href
677
+ : input && typeof input.url === 'string'
678
+ ? input.url
679
+ : String(input);
680
+ const parsed = new URL(url, resourceBase());
681
+ // DSH 官方 client-connection 使用 http://dsh.internal 作为逻辑 API 基址;
682
+ // 这是 iframe 内的虚拟地址,必须和 remote.invalid 一样转入 Web Tunnel,
683
+ // 不能让浏览器按真实网络请求处理并被 CSP 拦截。
684
+ if (parsed.origin === location.origin || parsed.origin === 'null' || parsed.origin === 'https://dsh.remote.invalid' || parsed.origin === 'http://dsh.internal') {
685
+ const request = typeof input === 'string' ? undefined : input;
686
+ const method = init?.method ?? request?.method;
687
+ const headers = init?.headers ?? request?.headers;
688
+ let body = typeof init?.body === 'string' ? init.body : undefined;
689
+ if (body === undefined && init?.body instanceof URLSearchParams) body = init.body.toString();
690
+ if (body === undefined && init === undefined && request && request.method !== 'GET' && request.method !== 'HEAD') {
691
+ body = await request.clone().text();
692
+ }
693
+ const response = await call('fetch', { path: parsed.pathname + parsed.search, method, headers: headers ? [...new Headers(headers).entries()] : undefined }, body);
694
+ return new Response(response.body, { status: response.status, headers: response.headers });
695
+ }
696
+ return originalFetch(input, init);
697
+ };
698
+ window.__dshRemoteSockets = new Map();
699
+ const RemoteWebSocket = class {
700
+ static CONNECTING = 0;
701
+ static OPEN = 1;
702
+ static CLOSING = 2;
703
+ static CLOSED = 3;
704
+ constructor(url) {
705
+ this.url = String(url);
706
+ this.readyState = RemoteWebSocket.CONNECTING;
707
+ this._listeners = new Map();
708
+ window.__dshRemoteSockets.set(this._id = String(++nextSocketId), this);
709
+ bridgeLog('bridge.ws.open', { id: this._id, path: new URL(this.url, resourceBase()).pathname });
710
+ call('ws.open', { path: new URL(this.url, resourceBase()).pathname }, undefined, this._id).then(() => {
711
+ if (this.readyState !== RemoteWebSocket.CONNECTING) return;
712
+ this.readyState = RemoteWebSocket.OPEN;
713
+ bridgeLog('bridge.ws.open.response', { id: this._id });
714
+ this._emit('open', new Event('open'));
715
+ }).catch((error) => {
716
+ bridgeLog('bridge.ws.open.error', { id: this._id, error: error instanceof Error ? error.message : String(error) });
717
+ this.readyState = RemoteWebSocket.CLOSED;
718
+ this._emit('error', new Error(error));
719
+ });
720
+ }
721
+ send(value) {
722
+ if (this.readyState !== RemoteWebSocket.OPEN) throw new Error('WebSocket is not open');
723
+ bridgeLog('bridge.ws.send', { id: this._id, bytes: typeof value === 'string' ? new TextEncoder().encode(value).byteLength : value?.byteLength, encoding: typeof value === 'string' ? 'text' : 'binary' });
724
+ parent.postMessage({ kind: 'ws.send', id: this._id, body: typeof value === 'string' ? value : value }, '*');
725
+ }
726
+ close(code, reason) {
727
+ if (this.readyState === RemoteWebSocket.CLOSING || this.readyState === RemoteWebSocket.CLOSED) return;
728
+ this.readyState = RemoteWebSocket.CLOSING;
729
+ bridgeLog('bridge.ws.close', { id: this._id, code: code || 1000 });
730
+ parent.postMessage({ kind: 'ws.close', id: this._id, input: { code, reason } }, '*');
731
+ this.readyState = RemoteWebSocket.CLOSED;
732
+ this._emit('close', new CloseEvent('close', { code: code || 1000, reason: reason || '' }));
733
+ }
734
+ addEventListener(type, listener, options) {
735
+ const listeners = this._listeners.get(type) || [];
736
+ if (!listeners.some((item) => item.listener === listener)) listeners.push({ listener, once: options?.once === true });
737
+ this._listeners.set(type, listeners);
738
+ }
739
+ removeEventListener(type, listener) {
740
+ const listeners = this._listeners.get(type) || [];
741
+ this._listeners.set(type, listeners.filter((item) => item.listener !== listener));
742
+ }
743
+ _emit(type, event) {
744
+ const listeners = [...(this._listeners.get(type) || [])];
745
+ for (const item of listeners) {
746
+ try { item.listener.call(this, event); } catch (error) { setTimeout(() => { throw error; }, 0); }
747
+ if (item.once) this.removeEventListener(type, item.listener);
748
+ }
749
+ const handler = this['on' + type];
750
+ if (typeof handler === 'function') handler.call(this, event);
751
+ }
752
+ };
753
+ window.WebSocket = RemoteWebSocket;
754
+ const openRemoteStream = (endpoint, payload, signal) => {
755
+ const streamId = 'remote_' + String(++nextStreamId) + '_' + Math.random().toString(36).slice(2);
756
+ return (async function*() {
757
+ const socket = new RemoteWebSocket(new URL('/api/remote.mux', resourceBase()).href);
758
+ const frames = [];
759
+ let wake;
760
+ let ended = false;
761
+ let failure;
762
+ const notify = () => { const resolve = wake; wake = undefined; resolve?.(); };
763
+ const onMessage = (event) => {
764
+ try {
765
+ const frame = JSON.parse(String(event.data));
766
+ if (!frame || frame.streamId !== streamId) return;
767
+ if (frame.type === 'end') ended = true;
768
+ else if (frame.type === 'error') failure = new Error(frame.error?.message || '远程 DSH Stream 失败');
769
+ else if (frame.type === 'item') frames.push(frame.value);
770
+ notify();
771
+ } catch (error) {
772
+ failure = error instanceof Error ? error : new Error(String(error));
773
+ notify();
774
+ }
775
+ };
776
+ const onError = () => { failure = new Error('远程 DSH Stream WebSocket 失败'); notify(); };
777
+ const onAbort = () => { failure = signal.reason instanceof Error ? signal.reason : new Error('远程 DSH Stream 已取消'); notify(); };
778
+ socket.addEventListener('message', onMessage);
779
+ signal?.addEventListener('abort', onAbort, { once: true });
780
+ try {
781
+ await new Promise((resolve, reject) => {
782
+ const opened = () => resolve(undefined);
783
+ const failed = () => reject(new Error('远程 DSH Stream WebSocket 打开失败'));
784
+ socket.addEventListener('open', opened, { once: true });
785
+ socket.addEventListener('error', failed, { once: true });
786
+ });
787
+ socket.addEventListener('error', onError);
788
+ signal?.throwIfAborted();
789
+ socket.send(JSON.stringify({ type: 'open', streamId, endpoint, payload }));
790
+ while (!ended) {
791
+ if (failure) throw failure;
792
+ if (frames.length === 0) await new Promise((resolve) => { wake = resolve; });
793
+ while (frames.length > 0) yield frames.shift();
794
+ }
795
+ if (failure) throw failure;
796
+ } finally {
797
+ signal?.removeEventListener('abort', onAbort);
798
+ socket.close(1000, 'stream closed');
799
+ }
800
+ })();
801
+ };
802
+ const parentTransport = (() => {
803
+ try { return parent.__CODINGNS4DSH_REMOTE_TRANSPORT__; } catch { return undefined; }
804
+ })();
805
+ globalThis.__DSH_TRANSPORT__ = {
806
+ fetch: window.fetch.bind(window),
807
+ openStream: openRemoteStream,
808
+ // 该 iframe 的所有 DSH 请求都经由已认证的 Codingns4DSH 隧道回到选定 Host。
809
+ // 必须声明 Host 所有权,否则 ui-settings 会把远程页面降级为 memory
810
+ // 模式,原生“模型”页无法读取 settings provider。
811
+ ownsHost: true,
812
+ generation: parentTransport?.getGeneration?.bind(parentTransport),
813
+ onGenerationChange: parentTransport?.onGenerationChange?.bind(parentTransport),
814
+ reconnect: parentTransport?.reconnect?.bind(parentTransport),
815
+ close: parentTransport?.close?.bind(parentTransport),
816
+ };
817
+ globalThis.__CODINGNS4DSH_DEBUG__ = (event, fields = {}) => {
818
+ bridgeLog('client.' + String(event), fields);
819
+ };
820
+ const NativeEventSource = globalThis.EventSource;
821
+ class RemoteEventSource {
822
+ static CONNECTING = 0;
823
+ static OPEN = 1;
824
+ static CLOSED = 2;
825
+ constructor(url) {
826
+ this.url = String(url);
827
+ this.readyState = 0;
828
+ this.withCredentials = false;
829
+ this._listeners = new Map();
830
+ const path = new URL(this.url, resourceBase()).pathname;
831
+ // HMR 的 /plugins/events 只负责开发期热更新;远程 Web 通过 Tunnel
832
+ // 运行时没有可用的 SSE 端点,模拟已打开的空事件源即可避免它触发
833
+ // DSH 主连接的重试逻辑。其它 EventSource 仍交给原生实现。
834
+ if (path === '/plugins/events') {
835
+ queueMicrotask(() => {
836
+ this.readyState = 1;
837
+ this._emit('open', new Event('open'));
838
+ });
839
+ return;
840
+ }
841
+ if (typeof NativeEventSource !== 'function') throw new Error('远程 DSH Web 不支持 EventSource');
842
+ this._native = new NativeEventSource(this.url);
843
+ this.readyState = this._native.readyState;
844
+ }
845
+ addEventListener(type, listener) {
846
+ const list = this._listeners.get(type) || [];
847
+ list.push(listener);
848
+ this._listeners.set(type, list);
849
+ this._native?.addEventListener(type, listener);
850
+ }
851
+ removeEventListener(type, listener) {
852
+ const list = this._listeners.get(type) || [];
853
+ this._listeners.set(type, list.filter((item) => item !== listener));
854
+ this._native?.removeEventListener(type, listener);
855
+ }
856
+ _emit(type, event) {
857
+ for (const listener of this._listeners.get(type) || []) listener.call(this, event);
858
+ const handler = this['on' + type];
859
+ if (typeof handler === 'function') handler.call(this, event);
860
+ }
861
+ close() {
862
+ this.readyState = 2;
863
+ this._native?.close();
864
+ }
865
+ }
866
+ globalThis.EventSource = RemoteEventSource;
867
+ })();`;
868
+ }
869
+ function resolveRemotePath(value) {
870
+ const parsed = new URL(value, 'https://dsh.remote.invalid');
871
+ if (parsed.origin !== 'https://dsh.remote.invalid' || parsed.pathname.includes('..'))
872
+ throw new Error('远程 DSH Web 路径无效');
873
+ return parsed.pathname + parsed.search;
874
+ }
875
+ /**
876
+ * DSH Web 原本的 CSP 面向真实站点;srcdoc 中的插件代码已经被搬到 Blob,
877
+ * 动态请求也由父页面 Tunnel 代理,因此必须把不再成立的站点限制换成
878
+ * 适用于沙箱 iframe 的策略。connect-src 保持关闭,防止页面绕过 bridge 直连。
879
+ */
880
+ function relaxRemoteContentSecurityPolicy(documentValue) {
881
+ const policy = "default-src 'self' blob: data:; script-src 'self' blob: data: 'unsafe-inline' 'unsafe-eval'; style-src 'self' blob: data: 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self' blob: data:; media-src 'self' blob: data:; worker-src 'self' blob:; connect-src 'none'; frame-src 'self' blob: data:";
882
+ for (const node of documentValue.querySelectorAll('meta[http-equiv]')) {
883
+ if ((node.getAttribute('http-equiv') ?? '').toLowerCase() === 'content-security-policy')
884
+ node.setAttribute('content', policy);
885
+ }
886
+ }
887
+ function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }
888
+ function toBytes(value) {
889
+ if (value instanceof Uint8Array)
890
+ return value;
891
+ if (value instanceof ArrayBuffer)
892
+ return new Uint8Array(value);
893
+ if (ArrayBuffer.isView(value))
894
+ return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
895
+ throw new TypeError('远程 WebSocket 消息必须是二进制或字符串');
896
+ }
897
+ function decodeText(value) {
898
+ if (!(value instanceof Uint8Array))
899
+ throw new TypeError('远程 DSH Web 资源必须是二进制');
900
+ return new TextDecoder().decode(value);
901
+ }
902
+ function collectRelativeReferences(source, suffix) {
903
+ const found = new Set();
904
+ const pattern = /["'`]((?:\.\.?\/)[^"'`]+)["'`]/gu;
905
+ for (const match of source.matchAll(pattern)) {
906
+ const reference = match[1];
907
+ if (reference !== undefined && suffix.test(reference))
908
+ found.add(reference);
909
+ }
910
+ return [...found];
911
+ }
912
+ function collectCssReferences(source, suffix) {
913
+ const found = new Set();
914
+ const pattern = /url\(\s*(?:"([^"]+)"|'([^']+)'|([^)'\s]+))\s*\)/gu;
915
+ for (const match of source.matchAll(pattern)) {
916
+ const reference = match[1] ?? match[2] ?? match[3];
917
+ if (reference?.startsWith('./') === true || reference?.startsWith('../') === true) {
918
+ if (suffix.test(reference))
919
+ found.add(reference);
920
+ }
921
+ }
922
+ return [...found];
923
+ }
924
+ function resolveRelativeAssetPath(sourcePath, reference) {
925
+ const resolved = new URL(reference, `https://dsh.remote.invalid${sourcePath}`);
926
+ return resolveRemotePath(resolved.pathname + resolved.search);
927
+ }
928
+ function contentTypeForPath(path) {
929
+ if (/\.woff2?(?:$|\?)/u.test(path))
930
+ return 'font/woff';
931
+ if (/\.ttf(?:$|\?)/u.test(path))
932
+ return 'font/ttf';
933
+ if (/\.svg(?:$|\?)/u.test(path))
934
+ return 'image/svg+xml';
935
+ if (/\.png(?:$|\?)/u.test(path))
936
+ return 'image/png';
937
+ return 'application/octet-stream';
938
+ }
939
+ //# sourceMappingURL=remote-web-context.js.map