@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.
- package/README.md +1114 -0
- package/assets/provider-icons/claude-code.png +0 -0
- package/assets/provider-icons/codex.png +0 -0
- package/assets/provider-icons/command-code.svg +5 -0
- package/assets/provider-icons/deepseek-harness.svg +9 -0
- package/assets/provider-icons/gemini.png +0 -0
- package/assets/provider-icons/grok.png +0 -0
- package/assets/provider-icons/kimi.png +0 -0
- package/assets/provider-icons/opencode.png +0 -0
- package/assets/provider-icons/pi.svg +7 -0
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +97 -0
- package/data/build/dist/bootstrap/dsh-connection-adapter.js +169 -0
- package/data/build/dist/bootstrap/index.d.ts +38 -0
- package/data/build/dist/bootstrap/index.js +44 -0
- package/data/build/dist/client/account-bar.d.ts +9 -0
- package/data/build/dist/client/account-bar.js +398 -0
- package/data/build/dist/client/bundle.js +26167 -0
- package/data/build/dist/client/cli-catalog.d.ts +24 -0
- package/data/build/dist/client/cli-catalog.js +108 -0
- package/data/build/dist/client/cli-slots.d.ts +38 -0
- package/data/build/dist/client/cli-slots.js +297 -0
- package/data/build/dist/client/debug/ui.d.ts +12 -0
- package/data/build/dist/client/debug/ui.js +451 -0
- package/data/build/dist/client/dsh-h5-bootstrap.d.ts +64 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +293 -0
- package/data/build/dist/client/dsh-runtime-version.d.ts +3 -0
- package/data/build/dist/client/dsh-runtime-version.js +11 -0
- package/data/build/dist/client/external-tool-stream.d.ts +24 -0
- package/data/build/dist/client/external-tool-stream.js +114 -0
- package/data/build/dist/client/features/cli-adapters.d.ts +7 -0
- package/data/build/dist/client/features/cli-adapters.js +215 -0
- package/data/build/dist/client/features/debug.d.ts +4 -0
- package/data/build/dist/client/features/debug.js +24 -0
- package/data/build/dist/client/features/index.d.ts +33 -0
- package/data/build/dist/client/features/index.js +44 -0
- package/data/build/dist/client/features/lan-access-panel.d.ts +5 -0
- package/data/build/dist/client/features/lan-access-panel.js +146 -0
- package/data/build/dist/client/features/lan-access.d.ts +9 -0
- package/data/build/dist/client/features/lan-access.js +30 -0
- package/data/build/dist/client/features/login-protection-panel.d.ts +5 -0
- package/data/build/dist/client/features/login-protection-panel.js +91 -0
- package/data/build/dist/client/features/login-protection-session.d.ts +6 -0
- package/data/build/dist/client/features/login-protection-session.js +25 -0
- package/data/build/dist/client/features/login-protection.d.ts +4 -0
- package/data/build/dist/client/features/login-protection.js +23 -0
- package/data/build/dist/client/features/reverse-proxy-panel.d.ts +9 -0
- package/data/build/dist/client/features/reverse-proxy-panel.js +159 -0
- package/data/build/dist/client/features/reverse-proxy.d.ts +12 -0
- package/data/build/dist/client/features/reverse-proxy.js +112 -0
- package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +5 -0
- package/data/build/dist/client/features/terminal-enhancement-panel.js +160 -0
- package/data/build/dist/client/features/terminal-enhancement.d.ts +4 -0
- package/data/build/dist/client/features/terminal-enhancement.js +24 -0
- package/data/build/dist/client/features/types.d.ts +58 -0
- package/data/build/dist/client/features/types.js +2 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +5 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +66 -0
- package/data/build/dist/client/features/workspace-session-enhancement.d.ts +4 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js +101 -0
- package/data/build/dist/client/h5-bootstrap-entry.d.ts +2 -0
- package/data/build/dist/client/h5-bootstrap-entry.js +17 -0
- package/data/build/dist/client/index.d.ts +31 -0
- package/data/build/dist/client/index.js +98 -0
- package/data/build/dist/client/lan-access.d.ts +28 -0
- package/data/build/dist/client/lan-access.js +68 -0
- package/data/build/dist/client/locale.d.ts +20 -0
- package/data/build/dist/client/locale.js +393 -0
- package/data/build/dist/client/provider-icon-assets.d.ts +2 -0
- package/data/build/dist/client/provider-icon-assets.js +23 -0
- package/data/build/dist/client/provider-icons.d.ts +19 -0
- package/data/build/dist/client/provider-icons.js +39 -0
- package/data/build/dist/client/remote-web-context.d.ts +57 -0
- package/data/build/dist/client/remote-web-context.js +939 -0
- package/data/build/dist/client/session-adapter-cache.d.ts +18 -0
- package/data/build/dist/client/session-adapter-cache.js +84 -0
- package/data/build/dist/client/settings-bridge.d.ts +32 -0
- package/data/build/dist/client/settings-bridge.js +126 -0
- package/data/build/dist/client/settings-section.d.ts +30 -0
- package/data/build/dist/client/settings-section.js +93 -0
- package/data/build/dist/client/subscription-slot.d.ts +18 -0
- package/data/build/dist/client/subscription-slot.js +223 -0
- package/data/build/dist/client/terminal/index.d.ts +4 -0
- package/data/build/dist/client/terminal/index.js +3 -0
- package/data/build/dist/client/terminal/model.d.ts +116 -0
- package/data/build/dist/client/terminal/model.js +435 -0
- package/data/build/dist/client/terminal/styles.d.ts +21 -0
- package/data/build/dist/client/terminal/styles.js +51 -0
- package/data/build/dist/client/terminal/ui.d.ts +19 -0
- package/data/build/dist/client/terminal/ui.js +257 -0
- package/data/build/dist/client/terminal/xterm-view.d.ts +13 -0
- package/data/build/dist/client/terminal/xterm-view.js +213 -0
- package/data/build/dist/client/theme.d.ts +56 -0
- package/data/build/dist/client/theme.js +186 -0
- package/data/build/dist/client/workspace-session-archive-dom.d.ts +29 -0
- package/data/build/dist/client/workspace-session-archive-dom.js +525 -0
- package/data/build/dist/client/workspace-session-fiber.d.ts +3 -0
- package/data/build/dist/client/workspace-session-fiber.js +33 -0
- package/data/build/dist/client/workspace-session-logo-dom.d.ts +26 -0
- package/data/build/dist/client/workspace-session-logo-dom.js +128 -0
- package/data/build/dist/features/index.d.ts +3 -0
- package/data/build/dist/features/index.js +2 -0
- package/data/build/dist/features/registry.d.ts +74 -0
- package/data/build/dist/features/registry.js +382 -0
- package/data/build/dist/features/resource-scope/index.d.ts +39 -0
- package/data/build/dist/features/resource-scope/index.js +161 -0
- package/data/build/dist/host/auth-session.d.ts +42 -0
- package/data/build/dist/host/auth-session.js +186 -0
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +103 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +45 -0
- package/data/build/dist/host/cli-adapters/codex-driver.js +534 -0
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +38 -0
- package/data/build/dist/host/cli-adapters/command-code-driver.js +394 -0
- package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +19 -0
- package/data/build/dist/host/cli-adapters/command-code-subscription.js +126 -0
- package/data/build/dist/host/cli-adapters/driver.d.ts +41 -0
- package/data/build/dist/host/cli-adapters/driver.js +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +37 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +180 -0
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +48 -0
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +373 -0
- package/data/build/dist/host/cli-adapters/feature.d.ts +7 -0
- package/data/build/dist/host/cli-adapters/feature.js +335 -0
- package/data/build/dist/host/cli-adapters/gemini-driver.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/gemini-driver.js +427 -0
- package/data/build/dist/host/cli-adapters/grok-driver.d.ts +38 -0
- package/data/build/dist/host/cli-adapters/grok-driver.js +372 -0
- package/data/build/dist/host/cli-adapters/http-sse-client.d.ts +22 -0
- package/data/build/dist/host/cli-adapters/http-sse-client.js +99 -0
- package/data/build/dist/host/cli-adapters/interaction-events.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/interaction-events.js +80 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +65 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +256 -0
- package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +21 -0
- package/data/build/dist/host/cli-adapters/kimi-driver.js +382 -0
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +22 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js +89 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +55 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +728 -0
- package/data/build/dist/host/cli-adapters/pi-driver.d.ts +43 -0
- package/data/build/dist/host/cli-adapters/pi-driver.js +312 -0
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +16 -0
- package/data/build/dist/host/cli-adapters/process-utils.js +27 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +82 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.js +610 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +95 -0
- package/data/build/dist/host/cli-adapters/registry.js +659 -0
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +119 -0
- package/data/build/dist/host/cli-adapters/session-probe.d.ts +20 -0
- package/data/build/dist/host/cli-adapters/session-probe.js +139 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +68 -0
- package/data/build/dist/host/cli-adapters/session-store.js +180 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +45 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +205 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +25 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +83 -0
- package/data/build/dist/host/cli-adapters/tool-observation.d.ts +9 -0
- package/data/build/dist/host/cli-adapters/tool-observation.js +39 -0
- package/data/build/dist/host/control-api-client.d.ts +84 -0
- package/data/build/dist/host/control-api-client.js +130 -0
- package/data/build/dist/host/credential-store.d.ts +54 -0
- package/data/build/dist/host/credential-store.js +114 -0
- package/data/build/dist/host/debug.d.ts +79 -0
- package/data/build/dist/host/debug.js +295 -0
- package/data/build/dist/host/dsh-device-runtime.d.ts +39 -0
- package/data/build/dist/host/dsh-device-runtime.js +118 -0
- package/data/build/dist/host/dsh-gateway-feature.d.ts +5 -0
- package/data/build/dist/host/dsh-gateway-feature.js +55 -0
- package/data/build/dist/host/dsh-runtime-version.d.ts +12 -0
- package/data/build/dist/host/dsh-runtime-version.js +69 -0
- package/data/build/dist/host/features/auth.d.ts +11 -0
- package/data/build/dist/host/features/auth.js +273 -0
- package/data/build/dist/host/features/debug.d.ts +5 -0
- package/data/build/dist/host/features/debug.js +90 -0
- package/data/build/dist/host/features/host-status.d.ts +12 -0
- package/data/build/dist/host/features/host-status.js +54 -0
- package/data/build/dist/host/features/index.d.ts +19 -0
- package/data/build/dist/host/features/index.js +29 -0
- package/data/build/dist/host/features/lan-access-dsh.d.ts +8 -0
- package/data/build/dist/host/features/lan-access-dsh.js +45 -0
- package/data/build/dist/host/features/terminal-process.d.ts +5 -0
- package/data/build/dist/host/features/terminal-process.js +110 -0
- package/data/build/dist/host/features/terminal-status.d.ts +18 -0
- package/data/build/dist/host/features/terminal-status.js +56 -0
- package/data/build/dist/host/features/types.d.ts +41 -0
- package/data/build/dist/host/features/types.js +2 -0
- package/data/build/dist/host/index.d.ts +43 -0
- package/data/build/dist/host/index.js +135 -0
- package/data/build/dist/host/lan-access-dsh.d.ts +83 -0
- package/data/build/dist/host/lan-access-dsh.js +831 -0
- package/data/build/dist/host/native-session-bridge.d.ts +130 -0
- package/data/build/dist/host/native-session-bridge.js +317 -0
- package/data/build/dist/host/relay-tunnel-runtime.d.ts +97 -0
- package/data/build/dist/host/relay-tunnel-runtime.js +361 -0
- package/data/build/dist/host/remote-web-runtime.d.ts +78 -0
- package/data/build/dist/host/remote-web-runtime.js +559 -0
- package/data/build/dist/host/rpc-table.d.ts +32 -0
- package/data/build/dist/host/rpc-table.js +47 -0
- package/data/build/dist/host/rpc.d.ts +18 -0
- package/data/build/dist/host/rpc.js +248 -0
- package/data/build/dist/host/settings.d.ts +19 -0
- package/data/build/dist/host/settings.js +80 -0
- package/data/build/dist/host/terminal/attachment-registry.d.ts +17 -0
- package/data/build/dist/host/terminal/attachment-registry.js +49 -0
- package/data/build/dist/host/terminal/backends/conpty-backend.d.ts +39 -0
- package/data/build/dist/host/terminal/backends/conpty-backend.js +160 -0
- package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts +36 -0
- package/data/build/dist/host/terminal/backends/local-pty-backend.js +178 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.js +191 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-client.d.ts +27 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-client.js +154 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-process.d.ts +10 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-process.js +147 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-protocol.d.ts +92 -0
- package/data/build/dist/host/terminal/broker/conpty-broker-protocol.js +134 -0
- package/data/build/dist/host/terminal/controller-factory.d.ts +37 -0
- package/data/build/dist/host/terminal/controller-factory.js +82 -0
- package/data/build/dist/host/terminal/index.d.ts +17 -0
- package/data/build/dist/host/terminal/index.js +17 -0
- package/data/build/dist/host/terminal/runtime-adapter.d.ts +77 -0
- package/data/build/dist/host/terminal/runtime-adapter.js +18 -0
- package/data/build/dist/host/terminal/runtime-manager.d.ts +41 -0
- package/data/build/dist/host/terminal/runtime-manager.js +179 -0
- package/data/build/dist/host/terminal/shell-detection.d.ts +23 -0
- package/data/build/dist/host/terminal/shell-detection.js +105 -0
- package/data/build/dist/host/terminal/startup.d.ts +33 -0
- package/data/build/dist/host/terminal/startup.js +100 -0
- package/data/build/dist/host/terminal/terminal-controller.d.ts +78 -0
- package/data/build/dist/host/terminal/terminal-controller.js +324 -0
- package/data/build/dist/host/terminal/terminal-process-service.d.ts +30 -0
- package/data/build/dist/host/terminal/terminal-process-service.js +194 -0
- package/data/build/dist/host/terminal/terminal-process-store.d.ts +45 -0
- package/data/build/dist/host/terminal/terminal-process-store.js +183 -0
- package/data/build/dist/host/terminal/terminal-service.d.ts +87 -0
- package/data/build/dist/host/terminal/terminal-service.js +528 -0
- package/data/build/dist/host/terminal/terminal-store.d.ts +49 -0
- package/data/build/dist/host/terminal/terminal-store.js +242 -0
- package/data/build/dist/index.d.ts +37 -0
- package/data/build/dist/index.js +26 -0
- package/data/build/dist/shared/contracts/auth.d.ts +122 -0
- package/data/build/dist/shared/contracts/auth.js +8 -0
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +192 -0
- package/data/build/dist/shared/contracts/cli-adapter.js +2 -0
- package/data/build/dist/shared/contracts/command-code.d.ts +5 -0
- package/data/build/dist/shared/contracts/command-code.js +2 -0
- package/data/build/dist/shared/contracts/config.d.ts +113 -0
- package/data/build/dist/shared/contracts/config.js +86 -0
- package/data/build/dist/shared/contracts/debug-config.d.ts +28 -0
- package/data/build/dist/shared/contracts/debug-config.js +78 -0
- package/data/build/dist/shared/contracts/debug.d.ts +75 -0
- package/data/build/dist/shared/contracts/debug.js +121 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +72 -0
- package/data/build/dist/shared/contracts/dsh-device.js +2 -0
- package/data/build/dist/shared/contracts/errors.d.ts +33 -0
- package/data/build/dist/shared/contracts/errors.js +41 -0
- package/data/build/dist/shared/contracts/feature.d.ts +71 -0
- package/data/build/dist/shared/contracts/feature.js +2 -0
- package/data/build/dist/shared/contracts/host-status.d.ts +14 -0
- package/data/build/dist/shared/contracts/host-status.js +2 -0
- package/data/build/dist/shared/contracts/lan-access-dsh.d.ts +30 -0
- package/data/build/dist/shared/contracts/lan-access-dsh.js +2 -0
- package/data/build/dist/shared/contracts/peer-host.d.ts +37 -0
- package/data/build/dist/shared/contracts/peer-host.js +9 -0
- package/data/build/dist/shared/contracts/signaling.d.ts +67 -0
- package/data/build/dist/shared/contracts/signaling.js +2 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +69 -0
- package/data/build/dist/shared/contracts/subscription.js +2 -0
- package/data/build/dist/shared/contracts/terminal-process.d.ts +65 -0
- package/data/build/dist/shared/contracts/terminal-process.js +2 -0
- package/data/build/dist/shared/contracts/terminal.d.ts +127 -0
- package/data/build/dist/shared/contracts/terminal.js +2 -0
- package/data/build/dist/shared/contracts/transport.d.ts +45 -0
- package/data/build/dist/shared/contracts/transport.js +3 -0
- package/data/build/dist/shared/contracts/version.d.ts +19 -0
- package/data/build/dist/shared/contracts/version.js +74 -0
- package/data/build/dist/shared/index.d.ts +22 -0
- package/data/build/dist/shared/index.js +9 -0
- package/data/build/dist/transport/carrier.d.ts +38 -0
- package/data/build/dist/transport/carrier.js +268 -0
- package/data/build/dist/transport/debug.d.ts +22 -0
- package/data/build/dist/transport/debug.js +57 -0
- package/data/build/dist/transport/dsh-envelope.d.ts +41 -0
- package/data/build/dist/transport/dsh-envelope.js +107 -0
- package/data/build/dist/transport/dsh-gateway.d.ts +64 -0
- package/data/build/dist/transport/dsh-gateway.js +254 -0
- package/data/build/dist/transport/dsh-session.d.ts +55 -0
- package/data/build/dist/transport/dsh-session.js +256 -0
- package/data/build/dist/transport/dsh-transport.d.ts +57 -0
- package/data/build/dist/transport/dsh-transport.js +125 -0
- package/data/build/dist/transport/frame.d.ts +132 -0
- package/data/build/dist/transport/frame.js +168 -0
- package/data/build/dist/transport/index.d.ts +11 -0
- package/data/build/dist/transport/index.js +11 -0
- package/data/build/dist/transport/multiplexer.d.ts +63 -0
- package/data/build/dist/transport/multiplexer.js +323 -0
- package/data/build/dist/transport/webrtc-client.d.ts +65 -0
- package/data/build/dist/transport/webrtc-client.js +255 -0
- package/data/build/dist/transport/webrtc-host.d.ts +58 -0
- package/data/build/dist/transport/webrtc-host.js +252 -0
- package/data/build/dist/typert.host.d.ts +25 -0
- package/data/build/dist/typert.host.js +150 -0
- package/dsh.bundle.patch +12 -0
- package/package.json +135 -0
- package/profile/README.md +27 -0
- package/profile/cordis.patch.yml +2 -0
- package/profile/package.json +20 -0
- package/profile/scripts/check-dsh-install.mjs +71 -0
- package/profile/version.json +5 -0
- package/scripts/check-dsh-install.mjs +88 -0
- package/version.json +6 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {} from './dsh-envelope.js';
|
|
2
|
+
import { DshSession } from './dsh-session.js';
|
|
3
|
+
import { createDshTransportDebugLogger } from './debug.js';
|
|
4
|
+
export const DSH_GATEWAY_PATH = '/__dsh__/transport/v1';
|
|
5
|
+
/** 将 DSH Session 后的 Envelope 按 streamId 路由到 FeatureRegistry 模块。 */
|
|
6
|
+
export class DshGateway {
|
|
7
|
+
options;
|
|
8
|
+
session;
|
|
9
|
+
streams = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* stream.open 的 feature 选择和本地资源创建可能异步完成。
|
|
12
|
+
* 取消帧可以在这段窗口内到达,必须先登记 opening,不能让取消穿透后又把旧流插入 streams。
|
|
13
|
+
*/
|
|
14
|
+
opening = new Set();
|
|
15
|
+
cancelledOpening = new Set();
|
|
16
|
+
maxStreams;
|
|
17
|
+
features;
|
|
18
|
+
sequence = 0;
|
|
19
|
+
started = false;
|
|
20
|
+
unsubscribe;
|
|
21
|
+
debug;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.maxStreams = options.maxStreams ?? 128;
|
|
25
|
+
this.debug = options.debug ?? createDshTransportDebugLogger({ component: 'gateway' });
|
|
26
|
+
this.features = options.features ?? [];
|
|
27
|
+
this.session = options.session ?? new DshSession({
|
|
28
|
+
carrier: options.carrier,
|
|
29
|
+
role: 'host',
|
|
30
|
+
generation: options.generation,
|
|
31
|
+
hostScope: options.hostScope,
|
|
32
|
+
acceptInitialGeneration: true,
|
|
33
|
+
...(options.dshVersion === undefined ? {} : { dshVersion: options.dshVersion }),
|
|
34
|
+
...(options.sessionOptions ?? {}),
|
|
35
|
+
debug: this.debug,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
start() {
|
|
39
|
+
if (this.started)
|
|
40
|
+
return;
|
|
41
|
+
this.started = true;
|
|
42
|
+
this.debug.log('gateway.start', { generation: this.session.generation, hostId: this.options.hostScope.hostId });
|
|
43
|
+
this.unsubscribe = this.session.subscribe((envelope) => { void this.route(envelope); });
|
|
44
|
+
this.session.start();
|
|
45
|
+
}
|
|
46
|
+
async close(reason = 'DSH Gateway 已关闭') {
|
|
47
|
+
if (!this.started)
|
|
48
|
+
return;
|
|
49
|
+
this.started = false;
|
|
50
|
+
this.debug.log('gateway.close', { reason, streams: this.streams.size });
|
|
51
|
+
this.unsubscribe?.();
|
|
52
|
+
this.unsubscribe = undefined;
|
|
53
|
+
for (const active of this.streams.values()) {
|
|
54
|
+
try {
|
|
55
|
+
await active.feature.handleMessage?.(active.context, { ...active.envelope, type: 'stream.cancel', meta: { reason } });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// 关闭阶段不再向远端传播业务错误,但必须继续清理其他流。
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.streams.clear();
|
|
62
|
+
this.opening.clear();
|
|
63
|
+
this.cancelledOpening.clear();
|
|
64
|
+
this.session.close(reason);
|
|
65
|
+
}
|
|
66
|
+
get path() { return DSH_GATEWAY_PATH; }
|
|
67
|
+
async route(envelope) {
|
|
68
|
+
this.debug.log('gateway.receive', envelopeDebugFields(envelope));
|
|
69
|
+
if (envelope.generation !== this.session.generation
|
|
70
|
+
|| envelope.hostScope.hostId !== this.options.hostScope.hostId
|
|
71
|
+
|| envelope.hostScope.kind !== this.options.hostScope.kind) {
|
|
72
|
+
this.debug.log('gateway.scope.drop', { expectedGeneration: this.session.generation, expectedHostId: this.options.hostScope.hostId, expectedHostKind: this.options.hostScope.kind, ...envelopeDebugFields(envelope) });
|
|
73
|
+
this.sendError(envelope, 'RESOURCE_SCOPE_STALE', '资源属于已经失效的 HostScope');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (envelope.type === 'stream.open') {
|
|
77
|
+
await this.openStream(envelope);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (envelope.type === 'stream.cancel' || envelope.type === 'stream.close') {
|
|
81
|
+
const active = this.streams.get(envelope.streamId);
|
|
82
|
+
if (active) {
|
|
83
|
+
try {
|
|
84
|
+
await active.feature.handleMessage?.(active.context, envelope);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
this.sendError(envelope, 'STREAM_FAILED', error instanceof Error ? error.message : 'DSH 流清理失败');
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
// Feature 清理失败也不能让已取消的流继续占用会话配额。
|
|
91
|
+
this.streams.delete(envelope.streamId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (this.opening.has(envelope.streamId)) {
|
|
95
|
+
// openStream 尚未完成时不能调用尚不存在的 context;记录取消,
|
|
96
|
+
// 由 openStream 在完成异步准备后丢弃资源并发送最终 close。
|
|
97
|
+
this.cancelledOpening.add(envelope.streamId);
|
|
98
|
+
}
|
|
99
|
+
this.send({ ...envelope, type: 'stream.close', sequence: this.nextSequence(), meta: {} });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const active = this.streams.get(envelope.streamId);
|
|
103
|
+
if (active === undefined) {
|
|
104
|
+
this.sendError(envelope, 'STREAM_LOST', '流不存在或已经关闭');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (envelope.type === 'stream.window') {
|
|
108
|
+
await active.feature.handleWindow?.(active.context, envelope);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await active.feature.handleMessage?.(active.context, envelope);
|
|
112
|
+
}
|
|
113
|
+
async openStream(envelope) {
|
|
114
|
+
if (this.streams.has(envelope.streamId)) {
|
|
115
|
+
this.sendError(envelope, 'MESSAGE_INVALID', 'streamId 已经使用');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (this.streams.size + this.opening.size >= this.maxStreams) {
|
|
119
|
+
this.debug.log('gateway.flow-control.rejected', {
|
|
120
|
+
activeStreams: this.streams.size,
|
|
121
|
+
openingStreams: this.opening.size,
|
|
122
|
+
maxStreams: this.maxStreams,
|
|
123
|
+
...envelopeDebugFields(envelope),
|
|
124
|
+
});
|
|
125
|
+
this.sendError(envelope, 'FLOW_CONTROL_INVALID', '超过会话流数量上限');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.opening.add(envelope.streamId);
|
|
129
|
+
let streamHandlerStarted = false;
|
|
130
|
+
try {
|
|
131
|
+
const feature = await this.findFeature(envelope);
|
|
132
|
+
if (!feature) {
|
|
133
|
+
this.debug.log('gateway.feature.missing', envelopeDebugFields(envelope));
|
|
134
|
+
this.sendError(envelope, 'FEATURE_DISABLED', '没有启用匹配的 DSH 功能模块');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (this.cancelledOpening.delete(envelope.streamId)) {
|
|
138
|
+
// 取消发生在 feature 选择期间,不能再启动本地 WebSocket 或其它子资源。
|
|
139
|
+
this.send({ ...envelope, type: 'stream.close', sequence: this.nextSequence(), meta: {} });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let context;
|
|
143
|
+
context = {
|
|
144
|
+
envelope,
|
|
145
|
+
session: this.session,
|
|
146
|
+
send: (type, meta = {}, body) => {
|
|
147
|
+
const message = {
|
|
148
|
+
...envelope,
|
|
149
|
+
messageId: `${envelope.streamId}_${this.nextSequence()}`,
|
|
150
|
+
type,
|
|
151
|
+
sequence: this.nextSequence(),
|
|
152
|
+
meta,
|
|
153
|
+
...(body === undefined ? {} : { body }),
|
|
154
|
+
};
|
|
155
|
+
this.send(message);
|
|
156
|
+
},
|
|
157
|
+
close: () => {
|
|
158
|
+
this.streams.delete(envelope.streamId);
|
|
159
|
+
this.send({ ...envelope, type: 'stream.close', sequence: this.nextSequence(), meta: {} });
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
this.streams.set(envelope.streamId, { envelope, feature, context });
|
|
163
|
+
// 进入 streams 后已经完成异步初始化,不能再同时计入 opening。
|
|
164
|
+
// 否则一个正在处理的流会被计算两次,实际并发达到上限一半就会误报流控错误。
|
|
165
|
+
this.opening.delete(envelope.streamId);
|
|
166
|
+
this.debug.log('gateway.stream.accepted', { ...envelopeDebugFields(envelope), feature: feature.operation ?? feature.channel ?? 'custom' });
|
|
167
|
+
this.send({ ...envelope, type: 'stream.accepted', sequence: this.nextSequence(), meta: { channel: envelope.channel } });
|
|
168
|
+
if (!feature.handleStream)
|
|
169
|
+
return;
|
|
170
|
+
streamHandlerStarted = true;
|
|
171
|
+
const result = await feature.handleStream(context);
|
|
172
|
+
if (result && Symbol.asyncIterator in Object(result)) {
|
|
173
|
+
for await (const message of result)
|
|
174
|
+
this.send(message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
this.debug.log('gateway.stream.error', { ...envelopeDebugFields(envelope), error: error instanceof Error ? error.message : String(error) });
|
|
179
|
+
if (this.streams.has(envelope.streamId)) {
|
|
180
|
+
this.sendError(envelope, 'STREAM_FAILED', error instanceof Error ? error.message : 'DSH 流处理失败');
|
|
181
|
+
this.streams.delete(envelope.streamId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
this.opening.delete(envelope.streamId);
|
|
186
|
+
this.cancelledOpening.delete(envelope.streamId);
|
|
187
|
+
if (streamHandlerStarted && this.streams.delete(envelope.streamId)) {
|
|
188
|
+
// handleStream 正常返回但遗漏 context.close 时,网关仍必须收敛流状态。
|
|
189
|
+
try {
|
|
190
|
+
this.send({ ...envelope, type: 'stream.close', sequence: this.nextSequence(), meta: {} });
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// 物理连接已关闭时,释放本地流状态优先于发送结束帧。
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async findFeature(envelope) {
|
|
199
|
+
for (const feature of this.features) {
|
|
200
|
+
if (feature.channel && feature.channel !== envelope.channel)
|
|
201
|
+
continue;
|
|
202
|
+
if (feature.operation && feature.operation !== envelope.meta.operation)
|
|
203
|
+
continue;
|
|
204
|
+
if (!feature.canHandle || await feature.canHandle(envelope))
|
|
205
|
+
return feature;
|
|
206
|
+
}
|
|
207
|
+
if (!this.options.registry)
|
|
208
|
+
return undefined;
|
|
209
|
+
for (const module of this.options.registry.modules()) {
|
|
210
|
+
const candidate = module;
|
|
211
|
+
if (!candidate.canHandle && !candidate.handleStream && !candidate.handleMessage && !candidate.handleWindow)
|
|
212
|
+
continue;
|
|
213
|
+
if (candidate.channel && candidate.channel !== envelope.channel)
|
|
214
|
+
continue;
|
|
215
|
+
if (candidate.operation && candidate.operation !== envelope.meta.operation)
|
|
216
|
+
continue;
|
|
217
|
+
if (!candidate.canHandle || await candidate.canHandle(envelope))
|
|
218
|
+
return candidate;
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
sendError(envelope, code, detail) {
|
|
223
|
+
this.debug.log('gateway.error', { code, detail, ...envelopeDebugFields(envelope) });
|
|
224
|
+
this.send({
|
|
225
|
+
...envelope,
|
|
226
|
+
messageId: `${envelope.messageId}_error`,
|
|
227
|
+
type: 'stream.error',
|
|
228
|
+
sequence: this.nextSequence(),
|
|
229
|
+
meta: { errorCode: code, detail, retryable: false },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
send(envelope) {
|
|
233
|
+
this.session.send(envelope);
|
|
234
|
+
}
|
|
235
|
+
nextSequence() {
|
|
236
|
+
if (this.sequence >= Number.MAX_SAFE_INTEGER)
|
|
237
|
+
throw new Error('DSH Gateway sequence 已耗尽');
|
|
238
|
+
return ++this.sequence;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function envelopeDebugFields(envelope) {
|
|
242
|
+
return {
|
|
243
|
+
type: envelope.type,
|
|
244
|
+
channel: envelope.channel,
|
|
245
|
+
streamId: envelope.streamId,
|
|
246
|
+
sequence: envelope.sequence,
|
|
247
|
+
generation: envelope.generation,
|
|
248
|
+
hostId: envelope.hostScope.hostId,
|
|
249
|
+
hostKind: envelope.hostScope.kind,
|
|
250
|
+
operation: typeof envelope.meta.operation === 'string' ? envelope.meta.operation : undefined,
|
|
251
|
+
bodyBytes: envelope.body?.byteLength ?? 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=dsh-gateway.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CodingNsCarrier } from './carrier.js';
|
|
2
|
+
import { type DshEnvelope, type DshHostScope } from './dsh-envelope.js';
|
|
3
|
+
import { type DshTransportDebugLogger } from './debug.js';
|
|
4
|
+
export type DshSessionRole = 'client' | 'host';
|
|
5
|
+
export type DshSessionState = 'idle' | 'handshaking' | 'ready' | 'degraded' | 'closed';
|
|
6
|
+
export interface DshSessionOptions {
|
|
7
|
+
carrier: CodingNsCarrier;
|
|
8
|
+
role: DshSessionRole;
|
|
9
|
+
generation: string;
|
|
10
|
+
hostScope: DshHostScope;
|
|
11
|
+
dshVersion?: string;
|
|
12
|
+
capabilities?: readonly string[];
|
|
13
|
+
protocol?: string;
|
|
14
|
+
heartbeatMs?: number;
|
|
15
|
+
onReady?(session: DshSession): void;
|
|
16
|
+
onEnvelope?(envelope: DshEnvelope): void;
|
|
17
|
+
onError?(error: Error): void;
|
|
18
|
+
/** Host 首个 session.hello 到达时采用对端 generation;后续帧仍严格校验。 */
|
|
19
|
+
acceptInitialGeneration?: boolean;
|
|
20
|
+
debug?: DshTransportDebugLogger;
|
|
21
|
+
}
|
|
22
|
+
/** 负责 DSH hello/ready、版本能力协商和心跳,不执行任何业务。 */
|
|
23
|
+
export declare class DshSession {
|
|
24
|
+
private readonly options;
|
|
25
|
+
private stateValue;
|
|
26
|
+
private readonly listeners;
|
|
27
|
+
private readonly unsubscribe;
|
|
28
|
+
private heartbeat;
|
|
29
|
+
private messageCounter;
|
|
30
|
+
private remoteCapabilities;
|
|
31
|
+
private readyValue;
|
|
32
|
+
private readyResolve;
|
|
33
|
+
private readyReject;
|
|
34
|
+
private readonly debug;
|
|
35
|
+
private generationValue;
|
|
36
|
+
constructor(options: DshSessionOptions);
|
|
37
|
+
get state(): DshSessionState;
|
|
38
|
+
get ready(): boolean;
|
|
39
|
+
get capabilities(): readonly string[];
|
|
40
|
+
get generation(): string;
|
|
41
|
+
start(): void;
|
|
42
|
+
waitReady(signal?: AbortSignal): Promise<void>;
|
|
43
|
+
subscribe(listener: (envelope: DshEnvelope) => void): () => void;
|
|
44
|
+
send(envelope: DshEnvelope): void;
|
|
45
|
+
close(reason?: string): void;
|
|
46
|
+
private sendHello;
|
|
47
|
+
private sendReady;
|
|
48
|
+
private receive;
|
|
49
|
+
private receiveSession;
|
|
50
|
+
private validateScope;
|
|
51
|
+
private createEnvelope;
|
|
52
|
+
private startHeartbeat;
|
|
53
|
+
private fail;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=dsh-session.d.ts.map
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { DSH_ENVELOPE_PROTOCOL, decodeDshEnvelope, encodeDshEnvelope, } from './dsh-envelope.js';
|
|
2
|
+
import { DSH_VERSION } from '../shared/contracts/version.js';
|
|
3
|
+
import { createDshTransportDebugLogger } from './debug.js';
|
|
4
|
+
/** 负责 DSH hello/ready、版本能力协商和心跳,不执行任何业务。 */
|
|
5
|
+
export class DshSession {
|
|
6
|
+
options;
|
|
7
|
+
stateValue = 'idle';
|
|
8
|
+
listeners = new Set();
|
|
9
|
+
unsubscribe;
|
|
10
|
+
heartbeat;
|
|
11
|
+
messageCounter = 0;
|
|
12
|
+
remoteCapabilities = [];
|
|
13
|
+
readyValue;
|
|
14
|
+
readyResolve;
|
|
15
|
+
readyReject;
|
|
16
|
+
debug;
|
|
17
|
+
generationValue;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.debug = options.debug ?? createDshTransportDebugLogger({ component: `session-${options.role}` });
|
|
21
|
+
this.generationValue = options.generation;
|
|
22
|
+
this.unsubscribe = options.carrier.subscribe((data) => this.receive(data));
|
|
23
|
+
if (options.onEnvelope)
|
|
24
|
+
this.listeners.add(options.onEnvelope);
|
|
25
|
+
}
|
|
26
|
+
get state() { return this.stateValue; }
|
|
27
|
+
get ready() { return this.stateValue === 'ready'; }
|
|
28
|
+
get capabilities() { return this.remoteCapabilities; }
|
|
29
|
+
get generation() { return this.generationValue; }
|
|
30
|
+
start() {
|
|
31
|
+
if (this.stateValue !== 'idle')
|
|
32
|
+
return;
|
|
33
|
+
this.stateValue = 'handshaking';
|
|
34
|
+
this.debug.log('session.start', { role: this.options.role, generation: this.generationValue, hostId: this.options.hostScope.hostId, hostKind: this.options.hostScope.kind });
|
|
35
|
+
this.readyValue = new Promise((resolve, reject) => {
|
|
36
|
+
this.readyResolve = resolve;
|
|
37
|
+
this.readyReject = reject;
|
|
38
|
+
});
|
|
39
|
+
// Host 侧通常只监听 onReady,不会调用 waitReady;即使远端作用域
|
|
40
|
+
// 无效,也不能让 ready Promise 变成 Node 的未处理拒绝。
|
|
41
|
+
void this.readyValue.catch(() => undefined);
|
|
42
|
+
if (this.options.role === 'client')
|
|
43
|
+
this.sendHello();
|
|
44
|
+
this.startHeartbeat();
|
|
45
|
+
}
|
|
46
|
+
waitReady(signal) {
|
|
47
|
+
if (this.ready)
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
if (this.stateValue === 'closed')
|
|
50
|
+
return Promise.reject(new Error('DSH Session 已关闭'));
|
|
51
|
+
if (!this.readyValue)
|
|
52
|
+
this.start();
|
|
53
|
+
const promise = this.readyValue;
|
|
54
|
+
if (!signal)
|
|
55
|
+
return promise;
|
|
56
|
+
if (signal.aborted)
|
|
57
|
+
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error('会话等待已取消'));
|
|
58
|
+
return Promise.race([
|
|
59
|
+
promise,
|
|
60
|
+
new Promise((_, reject) => signal.addEventListener('abort', () => reject(signal.reason instanceof Error ? signal.reason : new Error('会话等待已取消')), { once: true })),
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
subscribe(listener) {
|
|
64
|
+
this.listeners.add(listener);
|
|
65
|
+
return () => this.listeners.delete(listener);
|
|
66
|
+
}
|
|
67
|
+
send(envelope) {
|
|
68
|
+
if (this.stateValue === 'closed')
|
|
69
|
+
throw new Error('DSH Session 已关闭');
|
|
70
|
+
const pending = this.options.carrier.send(encodeDshEnvelope(envelope));
|
|
71
|
+
this.debug.log('session.send', envelopeDebugFields(envelope));
|
|
72
|
+
if (pending && typeof pending.catch === 'function') {
|
|
73
|
+
void pending.catch((error) => this.fail(error instanceof Error ? error : new Error(String(error))));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
close(reason = 'DSH Session 已关闭') {
|
|
77
|
+
if (this.stateValue === 'closed')
|
|
78
|
+
return;
|
|
79
|
+
this.stateValue = 'closed';
|
|
80
|
+
this.debug.log('session.close', { reason });
|
|
81
|
+
this.unsubscribe();
|
|
82
|
+
if (this.heartbeat)
|
|
83
|
+
clearInterval(this.heartbeat);
|
|
84
|
+
this.heartbeat = undefined;
|
|
85
|
+
this.readyReject?.(new Error(reason));
|
|
86
|
+
this.readyReject = undefined;
|
|
87
|
+
this.readyResolve = undefined;
|
|
88
|
+
}
|
|
89
|
+
sendHello() {
|
|
90
|
+
this.send(this.createEnvelope('session.hello', 'session', {
|
|
91
|
+
protocol: this.options.protocol ?? DSH_ENVELOPE_PROTOCOL,
|
|
92
|
+
dshVersion: this.options.dshVersion ?? DSH_VERSION,
|
|
93
|
+
capabilities: [...this.options.capabilities ?? []],
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
sendReady(capabilities) {
|
|
97
|
+
this.send(this.createEnvelope('session.ready', 'session', {
|
|
98
|
+
protocol: this.options.protocol ?? DSH_ENVELOPE_PROTOCOL,
|
|
99
|
+
dshVersion: this.options.dshVersion ?? DSH_VERSION,
|
|
100
|
+
capabilities: [...capabilities],
|
|
101
|
+
byteCredit: 64 * 1024,
|
|
102
|
+
messageCredit: 32,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
receive(data) {
|
|
106
|
+
if (this.stateValue === 'closed')
|
|
107
|
+
return;
|
|
108
|
+
let envelope;
|
|
109
|
+
try {
|
|
110
|
+
envelope = decodeDshEnvelope(data);
|
|
111
|
+
this.validateScope(envelope);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.debug.log('session.receive.invalid', { bytes: data.byteLength, error: error instanceof Error ? error.message : String(error) });
|
|
115
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.debug.log('session.receive', { bytes: data.byteLength, ...envelopeDebugFields(envelope) });
|
|
119
|
+
if (envelope.channel === 'session') {
|
|
120
|
+
this.receiveSession(envelope);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!this.ready) {
|
|
124
|
+
this.fail(new Error('SESSION_NOT_READY'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
for (const listener of [...this.listeners])
|
|
128
|
+
listener(envelope);
|
|
129
|
+
}
|
|
130
|
+
receiveSession(envelope) {
|
|
131
|
+
if (envelope.type === 'session.hello') {
|
|
132
|
+
if (this.options.role !== 'host' || envelope.sequence !== 0 || this.stateValue === 'ready') {
|
|
133
|
+
this.fail(new Error('非法 session.hello'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const protocol = envelope.meta.protocol;
|
|
137
|
+
const dshVersion = envelope.meta.dshVersion;
|
|
138
|
+
if (protocol !== (this.options.protocol ?? DSH_ENVELOPE_PROTOCOL) || dshVersion !== (this.options.dshVersion ?? DSH_VERSION)) {
|
|
139
|
+
this.fail(new Error('PROTOCOL_VERSION_UNSUPPORTED'));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const offered = readCapabilities(envelope.meta.capabilities);
|
|
143
|
+
const allowed = new Set(this.options.capabilities ?? offered);
|
|
144
|
+
this.remoteCapabilities = offered.filter((capability) => allowed.has(capability));
|
|
145
|
+
this.stateValue = 'ready';
|
|
146
|
+
this.debug.log('session.ready', { role: this.options.role, capabilities: this.remoteCapabilities });
|
|
147
|
+
this.sendReady(this.remoteCapabilities);
|
|
148
|
+
this.readyResolve?.();
|
|
149
|
+
this.options.onReady?.(this);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (envelope.type === 'session.ready') {
|
|
153
|
+
if (this.options.role !== 'client' || this.stateValue !== 'handshaking') {
|
|
154
|
+
this.fail(new Error('非法 session.ready'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const protocol = envelope.meta.protocol;
|
|
158
|
+
const dshVersion = envelope.meta.dshVersion;
|
|
159
|
+
if (protocol !== (this.options.protocol ?? DSH_ENVELOPE_PROTOCOL) || dshVersion !== (this.options.dshVersion ?? DSH_VERSION)) {
|
|
160
|
+
this.fail(new Error('PROTOCOL_VERSION_UNSUPPORTED'));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.remoteCapabilities = readCapabilities(envelope.meta.capabilities);
|
|
164
|
+
this.stateValue = 'ready';
|
|
165
|
+
this.debug.log('session.ready', { role: this.options.role, capabilities: this.remoteCapabilities });
|
|
166
|
+
this.readyResolve?.();
|
|
167
|
+
this.options.onReady?.(this);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (envelope.type === 'session.ping') {
|
|
171
|
+
this.send(this.createEnvelope('session.pong', 'session', { timestamp: Date.now() }));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (envelope.type === 'session.pong')
|
|
175
|
+
return;
|
|
176
|
+
if (envelope.type === 'session.close') {
|
|
177
|
+
this.close(typeof envelope.meta.reason === 'string' ? envelope.meta.reason : '远端关闭 DSH Session');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.fail(new Error('MESSAGE_INVALID'));
|
|
181
|
+
}
|
|
182
|
+
validateScope(envelope) {
|
|
183
|
+
const isInitialHello = this.options.role === 'host'
|
|
184
|
+
&& this.options.acceptInitialGeneration === true
|
|
185
|
+
&& this.stateValue === 'handshaking'
|
|
186
|
+
&& envelope.channel === 'session'
|
|
187
|
+
&& envelope.type === 'session.hello'
|
|
188
|
+
&& envelope.sequence === 0;
|
|
189
|
+
if (envelope.hostScope.hostId !== this.options.hostScope.hostId
|
|
190
|
+
|| envelope.hostScope.kind !== this.options.hostScope.kind
|
|
191
|
+
|| (!isInitialHello && envelope.generation !== this.generationValue)) {
|
|
192
|
+
throw new Error('RESOURCE_SCOPE_STALE');
|
|
193
|
+
}
|
|
194
|
+
if (isInitialHello && envelope.generation !== this.generationValue) {
|
|
195
|
+
this.debug.log('session.generation.adopt', {
|
|
196
|
+
previousGeneration: this.generationValue,
|
|
197
|
+
generation: envelope.generation,
|
|
198
|
+
hostId: envelope.hostScope.hostId,
|
|
199
|
+
});
|
|
200
|
+
this.generationValue = envelope.generation;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
createEnvelope(type, channel, meta) {
|
|
204
|
+
return {
|
|
205
|
+
version: 1,
|
|
206
|
+
messageId: `${this.options.role[0]}_${++this.messageCounter}`,
|
|
207
|
+
streamId: 'session',
|
|
208
|
+
channel,
|
|
209
|
+
type,
|
|
210
|
+
sequence: this.messageCounter - 1,
|
|
211
|
+
generation: this.generationValue,
|
|
212
|
+
hostScope: this.options.hostScope,
|
|
213
|
+
meta,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
startHeartbeat() {
|
|
217
|
+
const interval = this.options.heartbeatMs ?? 30_000;
|
|
218
|
+
if (!Number.isFinite(interval) || interval <= 0)
|
|
219
|
+
return;
|
|
220
|
+
this.heartbeat = setInterval(() => {
|
|
221
|
+
if (this.stateValue === 'closed')
|
|
222
|
+
return;
|
|
223
|
+
try {
|
|
224
|
+
this.send(this.createEnvelope('session.ping', 'session', { timestamp: Date.now() }));
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
228
|
+
}
|
|
229
|
+
}, interval);
|
|
230
|
+
}
|
|
231
|
+
fail(error) {
|
|
232
|
+
this.stateValue = 'degraded';
|
|
233
|
+
this.debug.log('session.error', { error: error.message });
|
|
234
|
+
this.readyReject?.(error);
|
|
235
|
+
this.options.onError?.(error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function envelopeDebugFields(envelope) {
|
|
239
|
+
return {
|
|
240
|
+
type: envelope.type,
|
|
241
|
+
channel: envelope.channel,
|
|
242
|
+
streamId: envelope.streamId,
|
|
243
|
+
sequence: envelope.sequence,
|
|
244
|
+
generation: envelope.generation,
|
|
245
|
+
hostId: envelope.hostScope.hostId,
|
|
246
|
+
hostKind: envelope.hostScope.kind,
|
|
247
|
+
operation: typeof envelope.meta.operation === 'string' ? envelope.meta.operation : undefined,
|
|
248
|
+
bodyBytes: envelope.body?.byteLength ?? 0,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function readCapabilities(value) {
|
|
252
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string'))
|
|
253
|
+
throw new Error('MESSAGE_INVALID');
|
|
254
|
+
return [...new Set(value)];
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=dsh-session.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CodingNsRpcRequest, CodingNsStreamRequest, CodingNsTransport, CodingNsTransportGeneration, CodingNsTransportHooks } from '../shared/index.js';
|
|
2
|
+
import type { CodingNsCarrier } from './carrier.js';
|
|
3
|
+
import type { TunnelFlowControl } from './multiplexer.js';
|
|
4
|
+
import type { DshHostScope } from './dsh-envelope.js';
|
|
5
|
+
import type { DshSession } from './dsh-session.js';
|
|
6
|
+
import type { DshTransportDebugLogger } from './debug.js';
|
|
7
|
+
export interface DshCodingNsTransportOptions {
|
|
8
|
+
carrier: CodingNsCarrier;
|
|
9
|
+
generation: CodingNsTransportGeneration;
|
|
10
|
+
ownsHost?: boolean;
|
|
11
|
+
streamBaseUrl?: string;
|
|
12
|
+
reconnect?: (signal?: AbortSignal) => Promise<void | CodingNsTransportGeneration>;
|
|
13
|
+
flowControl?: TunnelFlowControl;
|
|
14
|
+
hostScope?: DshHostScope;
|
|
15
|
+
session?: DshSession;
|
|
16
|
+
requireSessionReady?: boolean;
|
|
17
|
+
debug?: DshTransportDebugLogger;
|
|
18
|
+
}
|
|
19
|
+
/** 将 Tunnel Multiplexer 映射为 DSH ClientTransportHooks。 */
|
|
20
|
+
export declare class DshCodingNsTransport implements CodingNsTransport {
|
|
21
|
+
private readonly options;
|
|
22
|
+
private readonly listeners;
|
|
23
|
+
private readonly multiplexer;
|
|
24
|
+
private currentCarrier;
|
|
25
|
+
private generation;
|
|
26
|
+
constructor(options: DshCodingNsTransportOptions);
|
|
27
|
+
rpc<TResponse = unknown, TPayload = unknown>(request: CodingNsRpcRequest<TPayload>): Promise<TResponse>;
|
|
28
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
29
|
+
/** 向 Host 的 Remote Web Runtime 发送带明确 operation 的请求。 */
|
|
30
|
+
webRequest<TResponse = unknown, TPayload = unknown>(operation: string, payload: TPayload, signal?: AbortSignal): Promise<TResponse>;
|
|
31
|
+
/** 打开一个由 Remote Web Runtime 管理的 Web 流。 */
|
|
32
|
+
openWebStream<TChunk = unknown, TPayload = unknown>(operation: string, payload: TPayload, signal?: AbortSignal): AsyncIterable<TChunk>;
|
|
33
|
+
openWebStreamWithId<TChunk = unknown, TPayload = unknown>(operation: string, payload: TPayload, signal?: AbortSignal): {
|
|
34
|
+
streamId: string;
|
|
35
|
+
stream: AsyncIterable<TChunk>;
|
|
36
|
+
};
|
|
37
|
+
sendWebStream(streamId: string, type: string, body?: Uint8Array, meta?: Record<string, unknown>): void;
|
|
38
|
+
closeWebStream(streamId: string): void;
|
|
39
|
+
openStream<TChunk = unknown, TPayload = unknown>(request: CodingNsStreamRequest<TPayload>): AsyncIterable<TChunk>;
|
|
40
|
+
loadBundle(url: string): Promise<void>;
|
|
41
|
+
getGeneration(): CodingNsTransportGeneration | undefined;
|
|
42
|
+
onGenerationChange(listener: (generation: CodingNsTransportGeneration | undefined) => void): () => void;
|
|
43
|
+
reconnect(signal?: AbortSignal): Promise<void>;
|
|
44
|
+
/** 由启动胶水在建立新物理连接后提交新 generation。旧请求会立即失效。 */
|
|
45
|
+
updateGeneration(generation: CodingNsTransportGeneration): void;
|
|
46
|
+
/** 物理 WebRTC 重连后替换 carrier/session,并让 DSH Connection 看见新 generation。 */
|
|
47
|
+
replaceConnection(carrier: CodingNsCarrier, session: DshSession, generation: CodingNsTransportGeneration): void;
|
|
48
|
+
/** 物理连接失效时立即结束旧请求和旧 generation。 */
|
|
49
|
+
invalidateConnection(error?: Error): void;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
/** 给 pre-Cordis 启动胶水使用,不直接安装 DSH Connection。 */
|
|
52
|
+
asTransportHooks(): CodingNsTransportHooks & {
|
|
53
|
+
ownsHost?: boolean;
|
|
54
|
+
streamBaseUrl?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=dsh-transport.d.ts.map
|