@deepseek-ai/dsh-client-connection 0.2.1-alpha.1 → 0.2.1-alpha.3
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.i18n.yaml +8 -8
- package/README.md +14 -7
- package/README.zh.md +14 -7
- package/lib/{client.js → client.browser.js} +22 -2
- package/lib/client.node.js +624 -0
- package/lib/index.js +296 -74
- package/lib/types/api-request-trust.d.ts +16 -9
- package/lib/types/browser-auth.d.ts +25 -2
- package/lib/types/client/index.d.ts +19 -3
- package/lib/types/client/socket.d.ts +50 -0
- package/lib/types/connect-protocol.d.ts +6 -0
- package/lib/types/http-bridge.d.ts +3 -1
- package/lib/types/index.d.ts +9 -9
- package/lib/types/peer.d.ts +36 -0
- package/lib/types/rpc-host.d.ts +29 -7
- package/lib/types/rpc.d.ts +47 -11
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +22 -17
- package/lib/types/operator-peer.d.ts +0 -23
package/README.i18n.yaml
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
en: 18792bf2fb4f8030
|
|
16
16
|
zh: 84c63acc7e0099ae
|
|
17
17
|
/deepseek-ai-dsh-client-connection/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: da6810858bf74a86
|
|
19
|
+
zh: d623e510ec74520f
|
|
20
20
|
/deepseek-ai-dsh-client-connection/browser-authentication-and-request-trust:
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
21
|
+
en: e2154aa9caa859b4
|
|
22
|
+
zh: 32832efe4dc37c06
|
|
23
23
|
/deepseek-ai-dsh-client-connection/connection-generation:
|
|
24
|
-
en:
|
|
25
|
-
zh:
|
|
24
|
+
en: d9fe48c025ace7ce
|
|
25
|
+
zh: 0e23b299fbf25ae6
|
|
26
26
|
/deepseek-ai-dsh-client-connection/model-experience:
|
|
27
27
|
en: 88bbc33fe9ae9df8
|
|
28
28
|
zh: bfbab6d51b4abd40
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
en: ca75c51c89c2c9b0
|
|
31
31
|
zh: de54f3467ef3b9be
|
|
32
32
|
/deepseek-ai-dsh-client-connection/known-limitations-and-deferred-work:
|
|
33
|
-
en:
|
|
34
|
-
zh:
|
|
33
|
+
en: a860aa95302e4f02
|
|
34
|
+
zh: 4df79a08b5fe018e
|
|
35
35
|
/deepseek-ai-dsh-client-connection/known-limitations-and-deferred-work/dev-note:
|
|
36
36
|
en: e67f633dc6fe7773
|
|
37
37
|
zh: e88aeb617ffb74ea
|
package/README.md
CHANGED
|
@@ -25,31 +25,37 @@ The package carries browser-to-Host Remote calls, exact Fetch responses, and con
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
+
The `./client` import selects the standalone `lib/client.node.js` ESM bundle in Node and the `lib/client.browser.js` loader factory in browsers. The Web Host still serves the browser export at the logical `/plugins/.../client.js` URL. Import the Client entry for a non-page Cordis tree; the separate package root is the Host entry.
|
|
29
|
+
|
|
28
30
|
A static desktop page can provide `__DSH_TRANSPORT__.streamBaseUrl` for the HTTP origin of its owned Host. The Gateway uses that origin for its WebSocket while HTTP transport remains independently selected. The desktop carrier owns authentication; setting the origin alone grants no access.
|
|
29
31
|
|
|
30
32
|
Unary RPC requests use JSON. A Host handler may return byte attachments that it has already separated from the JSON-compatible result value, with each attachment naming its result-relative path. Connection writes these values as multipart parts. The JSON `metadata` part contains the RPC response envelope with `null` placeholders and an attachment table recording each path, codec, and part identifier. Paths use string keys and numeric array indices; no business field name is reserved. The Client validates the envelope, `rpcId`, attachment table, and parts, then restores each byte value as an `ArrayBuffer`-backed view. Results without attachments, including base64 strings, and failures remain JSON. Logical RPC carriers return decoded native values directly. Connection does not discover binary fields or depend on Typert; the handler that owns a result protocol performs any type-directed or runtime projection before returning. Binary parameters, events, and streamed binary results are unsupported.
|
|
31
33
|
|
|
32
|
-
The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; shell-owned compositions provide equivalent Remote streams, including a stream's uplink, through `connection.rpc.open` without opening a WebSocket. The browser plugin reads the page transport, recovery settings, and location, then delegates to `installConnection(ctx, options)`. A composition that owns its carrier may call the same installer directly; the whole-client test tier does so. Each invocation creates one Context-owned service, so several Client trees can use different carriers in one realm. The Host half always provides the carrier-neutral RPC and exact `GET`/`HEAD`/`POST` route registries. When a Web carrier is present it also owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks; a shell-owned carrier dispatches the shared Fetch handler directly. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
|
|
34
|
+
The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; shell-owned compositions provide equivalent Remote streams, including a stream's uplink, through `connection.rpc.open` without opening a WebSocket. The browser plugin reads the page transport, recovery settings, and location, then delegates to `installConnection(ctx, options)`. A composition that owns its carrier may call the same installer directly; the whole-client test tier does so. Non-page carriers supply `ClientTransportHooks.createWebSocket` with the same authenticated Cookie as HTTP and a `streamBaseUrl`; `ConnectionHandle.openWebSocket()` resolves the Mux path against that base and invokes the selected factory. Each invocation creates one Context-owned service, so several Client trees can use different carriers in one realm. The Host half always provides the carrier-neutral RPC and exact `GET`/`HEAD`/`POST` route registries. When a Web carrier is present it also owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks; a shell-owned carrier dispatches the shared Fetch handler directly. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
|
|
33
35
|
|
|
34
36
|
-----
|
|
35
37
|
|
|
36
38
|
<a id="browser-authentication-and-request-trust"></a>
|
|
37
39
|
## Browser authentication and request trust
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
Over the Web carrier, every Host RPC method and WebSocket stream requires an authenticated browser or API Peer; there is no method-specific loopback tier. Each process mints a random launch token. `dsh-web-app` prints and opens its application URL with `?token=...`, preserving the caller's authority and mount; `frontend-static` delegates root and index requests to `ctx.connection.authorizeIndex`, which accepts that token only on `GET /`, writes an authority-bound signed cookie, and redirects to clean `./`, which drops the token and keeps the request's directory. A missing, expired, malformed, or wrong-authority cookie returns 401 before RPC dispatch. Static assets remain public. The HTTP carrier accepts query tokens only at the root exchange and `/connect`, and accepts no Authorization-header token.
|
|
42
|
+
|
|
43
|
+
The cookie signing secret is the owner-scoped `client-connection/browser-session` grant record in `ctx.credentials`. The local provider persists it in `$DSH_HOME/.credentials.yaml`; `BrowserAuth` loads or creates the record during Connection activation and retains the secret in memory, so request authentication is synchronous. Deleting or replacing the record takes effect on the next Connection activation. Cookies carry an absolute issue/expiry interval, defaulting to 30 days through `cookieMaxAgeDays`, and bind the normalized hostname plus port in both their deterministic name and signed payload. They are host-only, `Path=/`, `HttpOnly`, and `SameSite=Strict`, and carry `Secure` exactly when the listener that received the index request serves TLS; only that listener's own protocol decides, never a `publicUrl` announcement or a forwarded header.
|
|
44
|
+
|
|
45
|
+
HTTPS cookie names and signed audiences include the `https:` scheme, so HTTP cookies cannot authenticate to a TLS listener even on the same hostname and port. Default ports normalize under the listener's scheme. Existing HTTP cookie audiences remain unchanged.
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
Before authentication, every request passes `src/api-request-trust.ts`. Its `Host` must be loopback, equal the listener's bind IP literal on any port, or match a `trustedHosts` entry: exact on `host:port`, any port on port-less entries. Host and configured entries use the listener's scheme for WHATWG default-port normalization, not a forwarded header or `Origin`. An attached HTTP(S) `Origin` must name the same authority, with Host parsed under that Origin's scheme to support HTTPS proxies over HTTP upstreams; `sec-fetch-site: cross-site` is refused. Malformed configured authorities fail plugin load. `allowsRemoteAuthorities` reports whether `trustedHosts` permits a non-loopback authority; accepting the bind IP does not change that list. These checks defend DNS rebinding and cross-site browser requests; they never establish identity. A failed Host/Origin check returns 403, while a trusted but unauthenticated request returns 401. `dsh web --host 0.0.0.0` is unsupported. Decision records: [browser request trust](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md) and [browser token authentication](../../../.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md).
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Every request speaks for one Peer, a `PeerScope` whose `principal` names the caller, whose `interactions` governs participation in forwarded events, and whose `signal` aborts when its scope ends. `ctx.connection.browserPeer` is shared by authenticated browser connections; it has owner principal and defaults to `interactions: 'answer'`. `ctx.connection.admit(request)` checks trust and browser or API authentication and returns the credential's live Peer or a rejection. The `/api` route and Gateway WebSocket upgrade use this admission; handlers receive the call's Peer. `ctx.connection.createPeer({ principal, interactions? })` creates an independent in-process Peer, disposed with the Context that read the service or earlier through its own `dispose()`; omitted interactions default to `answer`. The exported `Peer` class accepts the same options. `fetch(request, peer)` and the HTTP bridge take the Peer explicitly; an exact route answers 403 to a non-owner Peer. Method-level access for other principals belongs to [`dsh-api-remote-access`](../../api/remote-access/README.md).
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
Only API callers use `GET /connect?token=...&interactions=answer|none`: it immediately creates a Peer with owner principal and returns 302 with the Mux destination and a signed API Cookie. The API connection defaults to `none`; invalid or repeated declarations return 400. HTTP may precede WebSocket establishment. One API socket owns cleanup: duplicate attachment returns 409, closure removes its Peer, and later HTTP using that Cookie is refused. Already-admitted unary calls retain request-level cancellation, but `invocation.peer.ctx` cannot register effects after its Peer has been disposed. API callers keep their Cookie separate from browser storage; the authority-specific Cookie name is shared.
|
|
46
52
|
|
|
47
53
|
Authenticated shared HTTP requests pass through the `connection/request` waterfall before body transfer. A listener may refuse new requests or await `next()` through response completion; removing its owning fiber removes admission behavior. Desktop uses this hook to lock new API work during an approved installation without canceling already-admitted work. Client disconnection aborts the handler signal; the bridge stops socket writes and drains any remaining response chunks. WebSocket stream ownership remains with API Gateway.
|
|
48
54
|
|
|
49
55
|
<a id="connection-generation"></a>
|
|
50
56
|
## Connection generation
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
`installConnection` resolves `interactions` to `answer` for pages and `none` for non-page callers unless explicitly supplied. Only answering Clients register the internal `$events` logical stream as the sole generation source, independently of whether any `$on` listener exists. The Host attaches all incremental listeners in the API Remotes source factory, then sends one `{ type: 'ready', clientId, host: { home } }` item before events. `ConnectionController` publishes that generation and calls `onConnected` only after the ready item arrives, so baseline acquisition cannot race ahead of incremental observation.
|
|
53
59
|
|
|
54
60
|
An ended `$events` stream, a Remote stream error, a non-ready opening item, or a malformed event item invalidates the current generation. A pending handshake logs a slow-Host warning after 3 seconds and logs the readiness timeout and aborts after 15 seconds by default, including time spent waiting for the physical socket. The source must stop delivery, release resources, and settle after cancellation before a replacement starts; late readiness from a cancelled source cannot publish a generation. While the browser reports network availability, the controller publishes `connecting` and retries with 50%–100% jitter under caps of 500ms, 1s, 2s, 4s, 8s, and 10s, continuing at the final cap until recovery. Every retry asks Gateway to replace the physical WebSocket once and reopens `$events`.
|
|
55
61
|
|
|
@@ -72,7 +78,8 @@ None; this package neither assembles nor sends a provider request.
|
|
|
72
78
|
<a id="known-limitations-and-deferred-work"></a>
|
|
73
79
|
|
|
74
80
|
- **Buffered `/api` routes retain each request body in memory** — `maxRequestBodyBytes` (default 300 MiB, sized for the default 200 MiB aggregate image limit after base64 expansion plus envelope headroom) bounds ordinary image and RPC envelopes. Opt-in streaming routes receive backpressured chunks and bypass the aggregate cap; route implementations own persistence, cancellation, and any storage quota.
|
|
75
|
-
- **
|
|
81
|
+
- **A plain-HTTP listener exposes the bearer cookie in transit** — such a deployment cannot mark the browser cookie `Secure`, so serving the same authority over a network can leak the session cookie; configure TLS on the Web carrier ([`dsh-host-webserver`](../../host/webserver/README.md)) to have the cookie marked `Secure` and confined to TLS.
|
|
82
|
+
- **Unused API Peers may remain until Host shutdown** — no connect deadline or idle collection is imposed on lightweight records whose caller never establishes a WebSocket. Cookie expiry still rejects new requests. API callers must reconnect through `/connect` after their owning socket closes or the Host restarts.
|
|
76
83
|
- **There is no logout operation** — clearing the browser cookie ends one browser session; deleting the owner credential record and restarting `dsh` revokes every session.
|
|
77
84
|
|
|
78
85
|
|
package/README.zh.md
CHANGED
|
@@ -25,31 +25,37 @@ kind: "package-reference"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
+
`./client` 导入在 Node 中选择独立的 `lib/client.node.js` ESM bundle,在浏览器中选择 `lib/client.browser.js` loader factory。Web Host 仍以逻辑 URL `/plugins/.../client.js` 提供浏览器导出。非页面 Cordis 树应导入 Client 入口;独立的包根入口是 Host 入口。
|
|
29
|
+
|
|
28
30
|
静态桌面页面可以通过 `__DSH_TRANSPORT__.streamBaseUrl` 提供其所拥有 Host 的 HTTP origin。Gateway 将该 origin 用于 WebSocket,HTTP 传输仍独立选择。桌面载体负责认证;仅设置 origin 不会授予访问权限。
|
|
29
31
|
|
|
30
32
|
一元 RPC 请求使用 JSON。Host handler 可以返回已经从 JSON 兼容结果值中分离的字节附件,每个附件标明其相对于结果的路径。Connection 将这些值写为 multipart 部分。JSON `metadata` 部分包含带 `null` 占位值的 RPC 响应信封,以及记录各路径、codec 和部分标识符的附件表。路径使用字符串键与数字数组下标,不保留任何业务字段名。Client 校验信封、`rpcId`、附件表和各部分,再将每个字节值恢复为以 `ArrayBuffer` 为底层缓冲区的视图。没有附件的结果(包括 base64 字符串)与失败仍使用 JSON。逻辑 RPC 载体直接返回解码后的原生值。Connection 不识别二进制字段,也不依赖 Typert;拥有结果协议的 handler 在返回前执行按类型或按运行时值的投影。不支持二进制参数、事件和二进制流式结果。
|
|
31
33
|
|
|
32
|
-
浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。由 shell 持有的组合通过 `connection.rpc.open` 提供等价的 Remote 流(包括流的上行),不打开 WebSocket。浏览器插件读取页面 transport、恢复设置与 location,再委托 `installConnection(ctx, options)
|
|
34
|
+
浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。由 shell 持有的组合通过 `connection.rpc.open` 提供等价的 Remote 流(包括流的上行),不打开 WebSocket。浏览器插件读取页面 transport、恢复设置与 location,再委托 `installConnection(ctx, options)`。持有自身载体的组合可以直接调用同一个安装函数;整机客户端测试档就是这一消费者。非页面载体通过 `ClientTransportHooks.createWebSocket` 使用与 HTTP 相同的认证 Cookie,并提供 `streamBaseUrl`;`ConnectionHandle.openWebSocket()` 根据该 base 解析 Mux 路径,再调用所选工厂。每次调用都会创建一个归所属 Context 的服务,因此同一 realm 中的多棵 Client 树可以使用不同载体。Host half 始终提供与载体无关的 RPC 注册表和精确 `GET`/`HEAD`/`POST` 路由注册表。存在 Web 载体时,它还持有唯一 `/api` route、Fetch bridge、浏览器认证与 Host/Origin 校验;由 shell 持有的载体则直接分派共享 Fetch handler。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。
|
|
33
35
|
|
|
34
36
|
-----
|
|
35
37
|
|
|
36
38
|
<a id="browser-authentication-and-request-trust"></a>
|
|
37
39
|
## 浏览器认证与请求信任
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
通过 Web 载体访问时,每个 Host RPC 方法和 WebSocket 流都要求经过认证的浏览器或 API Peer,不存在按方法区分的 loopback 层。每个进程生成一个随机启动令牌。`dsh-web-app` 打印并打开带 `?token=...` 的应用 URL,保留调用方的 authority 与挂载;`frontend-static` 把根路径和 index 请求交给 `ctx.connection.authorizeIndex`,后者只在 `GET /` 接受该令牌,写入绑定 authority 的签名 cookie,再重定向到干净的 `./`,移除令牌并保留请求目录。缺失、过期、畸形或 authority 不匹配的 cookie 会在 RPC 分发前得到 401。静态资源保持公开。HTTP 载体只在根路径交换与 `/connect` 接受 query token,不接受 Authorization header token。
|
|
42
|
+
|
|
43
|
+
cookie 签名密钥是 `ctx.credentials` 中由 `client-connection/browser-session` 拥有的 grant 记录。本地提供方把它持久化到 `$DSH_HOME/.credentials.yaml`;`BrowserAuth` 在 Connection 激活期间加载或创建该记录,并把密钥留在内存中,因此请求认证同步执行。删除或替换该记录会在下一次 Connection 激活时生效。cookie 携带绝对签发与过期区间,`cookieMaxAgeDays` 默认设为 30 天,并在确定性名称与签名 payload 中同时绑定规范化 hostname 和 port。它是 host-only、`Path=/`、`HttpOnly`、`SameSite=Strict`;收到该 index 请求的监听器提供 TLS 时才带上 `Secure`,且只由该监听器自身的协议决定,`publicUrl` 声明与转发头都不参与判断。
|
|
44
|
+
|
|
45
|
+
HTTPS cookie 名称与签名 audience 包含 `https:` scheme,因此即使 hostname 和 port 相同,HTTP cookie 也无法通过 TLS 监听器的认证。默认端口按监听器的 scheme 归一化。既有 HTTP cookie audience 保持不变。
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
认证之前,每个请求都经过 `src/api-request-trust.ts`。其 `Host` 必须是 loopback、等于监听器的绑定 IP 字面量(端口不限),或与 `trustedHosts` 条目匹配:带端口的 `host:port` 精确匹配,不带端口的条目匹配任意端口。Host 与配置条目按监听器的 scheme 做 WHATWG 默认端口归一化,不采信转发 header 或 `Origin`。附带的 HTTP(S) `Origin` 必须指向同一 authority;此时按 Origin 的 scheme 解析 Host,以支持 HTTPS 代理转发到 HTTP 上游;`sec-fetch-site: cross-site` 一律拒绝。畸形配置 authority 会让插件加载失败。`allowsRemoteAuthorities` 报告 `trustedHosts` 是否允许非 loopback authority;接受绑定 IP 不会改变该列表。这些检查防御 DNS rebinding 与跨站浏览器请求,绝不建立身份。Host/Origin 校验失败返回 403;Host 可信但未认证的请求返回 401。`dsh web --host 0.0.0.0` 不受支持。决策记录:[浏览器请求信任](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md)与[浏览器令牌认证](../../../.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.zh.md)。
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
每个请求都代表一个 Peer:`principal` 指明调用方,`interactions` 决定能否参与转发事件,`signal` 在 scope 结束时中止。`ctx.connection.browserPeer` 由通过认证的浏览器连接共享,principal 为 owner,默认 `interactions: 'answer'`。`ctx.connection.admit(request)` 检查信任与浏览器或 API 认证,返回凭据对应的存活 Peer 或拒绝状态;`/api` 路由和 Gateway WebSocket 升级都使用该结果,处理器接收本次调用的 Peer。`ctx.connection.createPeer({ principal, interactions? })` 创建独立进程内 Peer,由读取该服务的 Context 或其自身的 `dispose()` 释放;省略 interactions 时默认 `answer`。导出的 `Peer` 类接受相同参数。`fetch(request, peer)` 和 HTTP bridge 必须显式接收 Peer;对非 owner Peer,exact route 返回 403。其他 principal 的方法级访问归 [`dsh-api-remote-access`](../../api/remote-access/README.zh.md)。
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
仅供 API 使用的 `GET /connect?token=...&interactions=answer|none` 立即创建 principal 为 owner 的 Peer,返回 302、Mux 目标地址与签名 API Cookie。API 连接默认 `none`,非法或重复声明返回 400。HTTP 可先于 WebSocket 建立。一条 API socket 负责清理:重复关联返回 409,关闭时移除其 Peer,之后使用该 Cookie 的 HTTP 被拒绝。已受理 unary 调用保留请求级取消,但 Peer 释放后 `invocation.peer.ctx` 不再接受新的 effect 注册。API 调用方将 Cookie 与浏览器存储分开;两者使用相同的 authority 专属 Cookie 名称。
|
|
46
52
|
|
|
47
53
|
通过认证的共享 HTTP 请求在传输请求体之前经过 `connection/request` waterfall。监听器可以拒绝新请求,或等待 `next()` 直到响应完成;释放所属 fiber 会移除准入行为。Desktop 使用此扩展点,在已批准的安装期间锁住新的 API 工作,而不取消已接纳的工作。客户端断开会中止处理函数的信号;桥接器停止写入 socket,并排空剩余响应块。WebSocket 流仍由 API Gateway 负责。
|
|
48
54
|
|
|
49
55
|
<a id="connection-generation"></a>
|
|
50
56
|
## Connection generation
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
未显式指定时,`installConnection` 将页面的 `interactions` 解析为 `answer`,非页面调用方解析为 `none`。只有回答型 Client 把内部 `$events` 逻辑流注册为唯一 generation source,与有无 `$on` 订阅无关。Host 在 API Remotes source factory 同步挂好所有增量 listener 后,先发送唯一 `{ type: 'ready', clientId, host: { home } }` 项,再发送事件。`ConnectionController` 仅在收到该 ready 项后发布 generation 并调用 `onConnected`,因此 baseline 不会跑在增量 listener 前面。
|
|
53
59
|
|
|
54
60
|
`$events` 结束、Remote 流报错、收到非 ready 首项或畸形事件项,都会使当前 generation 失效。默认情况下,挂起的握手在 3 秒后记录 Host 响应缓慢告警,在 15 秒后记录就绪超时并中止,包含等待物理 socket 的时间。取消后,source 必须停止投递、释放资源并结束,替换 source 才能启动;已取消 source 迟到的 ready 不能发布 generation。浏览器报告网络可用时,Controller 发布 `connecting`,并在 500ms、1s、2s、4s、8s 与 10s 上限内采用 50%–100% 抖动重试,达到终档后继续尝试直到恢复。每次重试都要求 Gateway 替换一次物理 WebSocket,再重开 `$events`。
|
|
55
61
|
|
|
@@ -72,7 +78,8 @@ API Gateway Client 把内部 `$events` 逻辑流注册为唯一 generation sourc
|
|
|
72
78
|
<a id="known-limitations-and-deferred-work"></a>
|
|
73
79
|
|
|
74
80
|
- **缓冲型 `/api` 路由会把每个请求体保留在内存里**:`maxRequestBodyBytes`(默认 300 MiB,按默认 200 MiB 图片总量上限经 base64 膨胀加信封余量得出)限制普通图片与 RPC 信封。显式启用的流式路由接收带背压的分块并绕过总量上限;路由实现负责持久化、取消与存储配额。
|
|
75
|
-
-
|
|
81
|
+
- **明文 HTTP 监听器会在传输中暴露 bearer cookie**:此类部署无法给浏览器 cookie 加 `Secure`;若把同一 authority 暴露到网络上,会话 cookie 可能泄露;在 Web 载体([`dsh-host-webserver`](../../host/webserver/README.zh.md))上配置 TLS 即可让 cookie 带上 `Secure` 并只经 TLS 发送。
|
|
82
|
+
- **未使用的 API Peer 可以保留到 Host 退出**:调用方从未建立 WebSocket 时,其轻量记录不受建联期限或空闲回收约束。Cookie 过期仍拒绝新请求。其生命周期 socket 关闭或 Host 重启后,API 调用方必须通过 `/connect` 重新接入。
|
|
76
83
|
- **没有 logout 操作**:清除浏览器 cookie 会结束单个浏览器会话;删除 owner 凭据记录并重启 `dsh` 会撤销全部会话。
|
|
77
84
|
|
|
78
85
|
|
|
@@ -1336,6 +1336,23 @@ window.__ModuleLoader__.load({
|
|
|
1336
1336
|
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
1337
1337
|
}
|
|
1338
1338
|
//#endregion
|
|
1339
|
+
//#region lib/types/client/socket.js
|
|
1340
|
+
/** Physical socket selection for browser, desktop, and authenticated Node connections. */
|
|
1341
|
+
/**
|
|
1342
|
+
* Resolve a logical channel against the explicit Host or the page's document base.
|
|
1343
|
+
* @param path - absolute logical channel path.
|
|
1344
|
+
* @param factory - authenticated socket factory; omission uses the browser API.
|
|
1345
|
+
* @param baseUrl - Host base URL, required outside a document.
|
|
1346
|
+
* @returns a socket owned by the calling channel.
|
|
1347
|
+
*/
|
|
1348
|
+
function openConnectionWebSocket(path, factory = (url) => new WebSocket(url), baseUrl) {
|
|
1349
|
+
const base = baseUrl ?? (typeof document === "undefined" ? void 0 : document.baseURI);
|
|
1350
|
+
if (base === void 0) throw new Error("connection: WebSocket carrier requires streamBaseUrl outside a document");
|
|
1351
|
+
const url = new URL(path.slice(1), base);
|
|
1352
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
1353
|
+
return factory(url.href);
|
|
1354
|
+
}
|
|
1355
|
+
//#endregion
|
|
1339
1356
|
//#region lib/types/client/index.js
|
|
1340
1357
|
/** Required services (none — this is the wire root). */
|
|
1341
1358
|
const inject = [];
|
|
@@ -1360,12 +1377,13 @@ window.__ModuleLoader__.load({
|
|
|
1360
1377
|
/**
|
|
1361
1378
|
* Install one Context-owned Connection service from explicit composition inputs.
|
|
1362
1379
|
* @param ctx - client Cordis context.
|
|
1363
|
-
* @param options - physical carrier, reconnect timing, and page location.
|
|
1380
|
+
* @param options - interaction participation, physical carrier, reconnect timing, and page location.
|
|
1364
1381
|
*/
|
|
1365
1382
|
function installConnection(ctx, options = {}) {
|
|
1366
1383
|
const pageLocation = options.location;
|
|
1367
1384
|
const transport = options.transport;
|
|
1368
1385
|
const recovery = options.recovery ?? {};
|
|
1386
|
+
const interactions = options.interactions ?? (pageLocation === void 0 ? "none" : "answer");
|
|
1369
1387
|
const rpc = transport?.rpc ?? createWebConnectionRpc(transport?.fetch, transport?.openStream);
|
|
1370
1388
|
let generationSource;
|
|
1371
1389
|
let owner;
|
|
@@ -1401,6 +1419,8 @@ window.__ModuleLoader__.load({
|
|
|
1401
1419
|
publishState(void 0);
|
|
1402
1420
|
};
|
|
1403
1421
|
const handle = {
|
|
1422
|
+
interactions,
|
|
1423
|
+
openWebSocket: (path) => openConnectionWebSocket(path, transport?.createWebSocket, transport?.streamBaseUrl),
|
|
1404
1424
|
isLoopback: transport?.ownsHost === true || pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname),
|
|
1405
1425
|
generation: {
|
|
1406
1426
|
getSnapshot: () => generation,
|
|
@@ -1500,4 +1520,4 @@ window.__ModuleLoader__.load({
|
|
|
1500
1520
|
}
|
|
1501
1521
|
});
|
|
1502
1522
|
|
|
1503
|
-
//# sourceMappingURL=client.js.map
|
|
1523
|
+
//# sourceMappingURL=client.browser.js.map
|