跳到主要内容

数据面连接:连到沙箱里的 envd

30 秒导读: 控制面(01)负责"造出一个沙箱";本章讲的是造好之后, SDK 怎么找到并连上那个沙箱内部跑着的 envd 守护进程,以及所有数据面调用 (执行命令、读写文件、监听目录)共用的那层连接与传输底座。不讲具体命令/文件语义 (那是 03 / 04),只讲"网线怎么接、怎么鉴权、 长连接的流怎么活着又怎么被回收"。


1. 这一章解决什么(零基础也能懂)

先厘清一个容易混淆的点:E2B 有两个要连的东西。

  • 控制面(Orchestrator API):一个中心化的 REST 服务,https://api.<domain>。你在这里 "创建/暂停/杀死沙箱"。所有沙箱共用这一个入口。
  • 数据面(envd):每个沙箱内部都跑着一个自己的守护进程 envd。你要执行命令、读文件, 得连到那一个具体沙箱envd,而不是中心 API。

本章只管第二件事。它要回答三个朴素的问题:

问题白话本章对应节
我怎么知道某个沙箱的 envd 在哪个地址?从 sandboxId 推导出一个主机名§3 URL 推导
连上去凭什么让它信任我?API key、access token、Basic 头§5 鉴权与头部
一条命令跑十分钟,这根"网线"怎么保持活着、什么时候该拆?握手超时 vs 空闲超时 vs 用户取消§6 流与超时

一句话直觉: 想象每个沙箱都有一个固定的门牌号,门牌号是算出来的(不用查表)—— 49983-<沙箱id>.e2b.app。SDK 拿到 sandboxId 就能拼出门牌,敲门时递上通行证, 然后维持一条"只在网线断了才报警、慢慢读消息不报警"的长连接。


2. 顶层全景(这层在哪、由谁组成)

数据面连接不是一个大类,而是四个协作的小模块。先看它们怎么串起来:

你的代码: sbx.commands.run("...")

┌───────────────────────────┴───────────────────────────┐
│ sandbox/index.ts 构造函数 │
│ 把下面四块"组装"成一个可用的连接 │
└───────────────────────────┬───────────────────────────┘
│ │ │
┌─────────────┘ ┌─────────┘ ┌─────────┘
▼ ▼ ▼
① 门牌号从哪来 ② 网线/连接池 ③ 出错了怎么翻译
connectionConfig.ts envd/http2.ts envd/rpc.ts + envd/api.ts
URL 推导 / 超时信号 createEnvd(Rpc)Fetch 错误码映射 + 探活消歧
getHost / getSignal allowH2 + 连接池 handleRpcError / /health
│ │ │
└───────────────────────┴───────────────────┘

┌────────────────────────────┐
│ 沙箱内部的 envd 守护进程 │
│ REST(/health,/files) + │
│ Connect-RPC(命令/watch) │
└────────────────────────────┘

四块各干什么(一句话):

模块文件职责
配置 & URL 推导src/connectionConfig.ts从 opts/环境变量取值;把 sandboxId 推成主机名;发超时/取消信号
传输 fetchsrc/envd/http2.ts造带 HTTP/2 + 连接池 + 代理的 fetch,REST 与 RPC 各一套
REST 客户端src/envd/api.tsopenapi-fetch 客户端、/health 探活、HTTP 码 → SDK 错误
RPC 错误层src/envd/rpc.tsConnect-RPC 状态码 → SDK 错误、断连消歧、Basic 鉴权头

主线走一遍(高层,不进代码): Sandbox 构造时用 ConnectionConfig 把 sandboxId 推成 envdApiUrl → 用 http2.ts 造两个 fetch(一个给 REST、一个给 RPC)→ 把它们分别包进 EnvdApiClient(REST)和 createConnectTransport(RPC),并在包的时候注入鉴权头 → 往后命令/文件/PTY 三个子系统都复用这套连接。


3. URL 推导:从 sandboxId 算出门牌号

这节讲:SDK 不去中心服务"查"某个沙箱在哪,而是纯字符串拼接算出地址。

3.1 核心公式:getHost

最底层的一步是 getHost——把 端口 + sandboxId + 域名 拼成一个主机名:

// connectionConfig.ts:451 getHost
getHost(sandboxId, port, sandboxDomain) {
if (this.debug) {
return `localhost:${port}` // 调试模式:直连本机 envd
}
return `${port}-${sandboxId}.${sandboxDomain ?? this.domain}`
}

也就是说线上一个沙箱的门牌长这样(getHost at connectionConfig.ts:451-457):

49983 - <sandboxId> . e2b.app
│ │ │
envd端口 哪个沙箱 域名(可换)

那个 49983 是 envd 的固定端口,作为常量写死:ConnectionConfig.envdPort = 49983connectionConfig.ts:335),沙箱实例上也有一份 envdPort = 49983sandbox/index.ts:111)。 把端口放进子域名(而不是 :49983 这种端口号)是关键——这样一个 HTTPS 443 入口就能靠 子域名路由到沙箱内的任意端口,浏览器/CDN 都能走。

3.2 两个入口:稳定域名 vs 直连

同一个沙箱,SDK 会算出两个 URL,用途不同:

方法返回什么用途
getSandboxUrl生产环境优先返回 https://sandbox.<domain> 这个稳定入口RPC/大多数调用的 baseUrl
getSandboxDirectUrl永远返回 https://<port>-<id>.<domain>直连主机需要绕过稳定入口的场景(如文件流)

getSandboxUrl 的分支逻辑(connectionConfig.ts:414-434):

// connectionConfig.ts:414 getSandboxUrl(简化)
if (this.sandboxUrl) return this.sandboxUrl // 显式覆盖优先
if (this.debug) return `http://${getHost(...)}` // 调试 → localhost
// 生产、非浏览器、且域名在白名单里 → 用稳定的 sandbox.<domain>
if (runtime !== 'browser' && supportedDomains.includes(sandboxDomain)) {
return `https://sandbox.${sandboxDomain}`
}
return `https://${getHost(sandboxId, envdPort, sandboxDomain)}` // 兜底:直连主机名

这里有两处刻意的取舍,都写在源码注释里:

  • 只有白名单域名走稳定入口。 supportedDomainsconnectionConfig.ts:6)目前是 ['e2b.app','e2b.dev','e2b.pro','e2b-staging.dev']。注释说明:稳定的 sandbox.<domain> 主机 只在 E2B 生产环境保证可用,其它自托管域名可能还没部署这个入口,所以不在白名单里的一律 回退到按 sandboxId 拼的直连主机名。
  • 浏览器里不走稳定入口。 条件里带 runtime !== 'browser'——注释点明是 sandbox.<domain> 在浏览器有 CORS 问题,所以浏览器端也回退到直连主机名。

getSandboxDirectUrlconnectionConfig.ts:436-449)则没有"稳定入口"这一步,除了 debug 和显式 sandboxUrl 覆盖,永远吐 https://<port>-<id>.<domain>。在 sandbox/index.ts:151 里 它被存成 envdDirectUrl,专门用于文件上传/下载那条 HTTP 数据通道(sandbox/index.ts:796new URL('/files', this.envdDirectUrl),细节见 04)。

3.3 debug 模式:一切指向 localhost

贯穿 §3 的暗线是 this.debug:一旦开启,URL 推导全部塌缩成 localhost——getHost 返回 localhost:<port>apiUrl 变成 http://localhost:3000connectionConfig.ts:372-375)。 这是给"本地跑一个 envd 做开发"用的,靠环境变量 E2B_DEBUG=true 打开。


4. ConnectionConfig:所有取值的唯一入口

这节讲:URL 里用到的域名、密钥、超时、代理,统一由 ConnectionConfig 从"显式参数 → 环境变量 → 默认值"三级兜底地解析出来。

4.1 取值优先级:opts > 环境变量 > 默认

构造函数(connectionConfig.ts:358-378)对每个字段都是同一个模式:先看你传的 opts, 再看环境变量,最后给个默认。整理成表:

字段opts环境变量默认
apiKeyapiKeyE2B_API_KEY
domaindomainE2B_DOMAINe2b.app
debugdebugE2B_DEBUGfalse
apiUrlapiUrlE2B_API_URLhttps://api.<domain>(debug 时 localhost:3000
sandboxUrlsandboxUrlE2B_SANDBOX_URL无(无则走推导)
accessTokenaccessTokenE2B_ACCESS_TOKEN无(已废弃,改用 apiHeaders
validateApiKeyvalidateApiKeyE2B_VALIDATE_API_KEYtrue
requestTimeoutMsrequestTimeoutMs60_000

环境变量的读取集中在几个私有静态 getter 里(connectionConfig.ts:380-408),底层都走 getEnvVarapi/metadata.ts:18,内部区分 Deno/Node 运行时)。

4.2 User-Agent 组装

ConnectionConfig 会把一个 User-Agent 头塞进 this.headersconnectionConfig.ts:369), 值由 buildUserAgent 拼(connectionConfig.ts:321-329):基础是 e2b-js-sdk/<version>, 如果调用方传了 integration(比如上层的 e2b-code-interpreter/0.1.0)就追加在后面。这样 服务端能从 UA 分辨请求是哪个 SDK/集成发的。

4.3 getSignal:一个方法产出取消信号

ConnectionConfig 唯一的"动作"方法是 getSignalconnectionConfig.ts:410-412),它只是 薄薄地转发给 buildRequestSignal——这是理解 §6 超时模型的入口,下节展开。


5. 鉴权与头部注入:谁在什么位置塞了什么

这节讲:一次数据面请求上带的头,来自三个不同层次,各管一件事。

E2B 数据面用了多把钥匙,不是一个 token 走天下。按注入位置分:

值/来源谁注入作用
Authorization: Basic <base64>authenticationHeaderRPC 调用点告诉 envd "以哪个 Linux 用户身份"执行
X-Access-TokenenvdAccessToken构造函数 & EnvdApiClient沙箱级 envd 访问令牌
E2b-Sandbox-Id / E2b-Sandbox-PortsandboxId / 49983构造函数 sandboxHeaders路由:让入口知道转发给哪个沙箱的哪个端口
User-AgentbuildUserAgentConnectionConfig标识 SDK/集成

5.1 Basic 头:base64("user:") 不是密码

最容易被误解的是 Authorization: Basic。它不是账号密码——authenticationHeaderenvd/rpc.ts:158-178)把用户名拼成 "<username>:"(冒号后空密码)再 base64, 用来告诉 envd "在沙箱里以哪个用户身份跑这条命令":

// envd/rpc.ts:158 authenticationHeader(简化)
// 老版本 envd 没有默认用户概念,缺省补 'user'
if (username == undefined && compareVersions(envdVersion, ENVD_DEFAULT_USER) < 0) {
username = defaultUsername // 'user'
}
if (!username) return {}
const encoded = encode64(`${username}:`) // base64("user:")
return { Authorization: `Basic ${encoded}` }

注意它还带版本兼容逻辑:只有当 envd 版本旧于 ENVD_DEFAULT_USER 时才补默认用户名, 新版本 envd 自己有默认用户就不强塞。encode64envd/rpc.ts:145)按运行时选 btoaBuffer.from(...).toString('base64')

5.2 构造函数里的头组装 + redirect 补丁

RPC 传输在 sandbox/index.ts 构造函数里用 createConnectTransport 建立 (sandbox/index.ts:166-198)。它的 fetch手动 patch 过,干两件事:

// sandbox/index.ts:166 createConnectTransport(要点)
const rpcTransport = createConnectTransport({
baseUrl: this.envdApiUrl,
useBinaryFormat: false,
fetch: (url, options) => {
const headers = new Headers({ 'User-Agent': ... })
// 合并 sandboxHeaders(E2b-Sandbox-Id / -Port)
new Headers(sandboxHeaders).forEach((v, k) => headers.append(k, v))
if (this.envdAccessToken) headers.append('X-Access-Token', this.envdAccessToken)
options = { ...options, headers, redirect: 'follow' } // ← 关键补丁
return envdRpcFetch(url, options)
},
})

为什么要 patch redirect: 'follow' 注释(sandbox/index.ts:170-174)讲得很清楚: connect-web 写死用 redirect: 'error',这在边缘运行时(edge runtime)不被支持;E2B 的端点 可以安全地跟随重定向,所以覆盖成 follow。这是为了让 SDK 能在 Vercel Edge 之类环境里跑。

sandboxHeaderssandbox/index.ts:159-162)只有两个字段——E2b-Sandbox-IdE2b-Sandbox-Port——这正是 §3.2 里"稳定入口 sandbox.<domain>"能路由到具体沙箱的凭据: URL 不含 sandboxId 了,就靠这两个头告诉入口"转给谁"。

REST 侧(EnvdApiClientsandbox/index.ts:200-214)也带同一套 sandboxHeaders + UA, 并在客户端内部注入 X-Access-Tokenenvd/api.ts:213-217)。


6. 流与超时的生命周期(本章精髓)

这节讲:数据面既有"一次性短请求"(读个文件),也有"活十分钟的长连接"(跑命令、watch 目录)。 这两者对"什么时候该断"的要求完全不同,SDK 用三个层层递进的原语解决。

6.1 直觉:一根网线的三种"该断"理由

先建立心智模型。一根连接可能因为三种原因该被中止:

理由A:整个请求太久了 → 一次性请求用(读文件超过 60s 就放弃)
理由B:握手迟迟不成功 → 长连接用(连都没连上,别无限等)
理由C:连上了但服务端不发了 → 长连接用(网线哑了,但别误伤"慢慢读的人")
─────────────────
另加:用户主动 abort → 任何时候都能取消

难点在 理由C:一个跑十分钟的命令,中间可能很久没输出——这到底是"网断了"还是"命令在 安静地干活/消费者读得慢"?误判就会杀掉正常任务。SDK 的答案是:只掐网线,不掐慢消费者

6.2 buildRequestSignal:合并"超时"和"用户取消"

最简单的原语,给一次性请求用。buildRequestSignalconnectionConfig.ts:124-138)把 "超时信号"和"用户信号"用 AbortSignal.any 合并成一个——任一触发就中止:

// connectionConfig.ts:124 buildRequestSignal
const timeoutSignal = requestTimeoutMs ? AbortSignal.timeout(requestTimeoutMs) : undefined
if (timeoutSignal && userSignal) return AbortSignal.any([timeoutSignal, userSignal])
return timeoutSignal ?? userSignal

requestTimeoutMs0(或 undefined)就是关掉超时。这是 §4.3 里 getSignal 的实体, 被大量一次性 REST 调用直接使用(如 sandbox/sandboxApi.ts 的众多 config.getSignal(...))。

6.3 setupRequestController:握手超时 vs 全程取消分离

长连接不能用 §6.2 那种"整段超时"——命令跑一小时不代表出错。setupRequestControllerconnectionConfig.ts:158-207)把一个 AbortController 拆成两段寿命

返回的东西干嘛
controller传给 fetch,实际控制中止
clearStartTimeout()握手成功后调用——拆掉"启动超时"这颗定时器,让流可以无限活下去
cleanup()幂等收尾——摘掉用户监听、清定时器、abort controller

关键点:requestTimeoutMs 那颗定时器只用来卡握手connectionConfig.ts:177-188), 握手一成功就 clearStartTimeout() 拆掉;而用户信号的监听器全程挂着connectionConfig.ts:169-175),所以你随时能 abort 一个长跑的流。这就把"连不上"和 "连上后长期运行"两种超时诉求分开了。

6.4 createIdleAbort + wrapStreamWithConnectionCleanup:只掐哑掉的网线

握手拆掉后,长连接怎么防"服务端中途不发了"?答案是一个可重置的空闲定时器 createIdleAbortconnectionConfig.ts:216-243):arm() 起表、clear() 停表,超时就 abort。它被 wrapStreamWithConnectionCleanupconnectionConfig.ts:259-319)用来包住流的 读取循环。精髓在 pull 里这几行(connectionConfig.ts:290-305):

// connectionConfig.ts:290 wrapStreamWithConnectionCleanup 的 pull(简化)
async pull(streamController) {
idle.arm() // 读网络前起表
const { done, value } = await reader.read()
idle.clear() // 一收到 chunk/EOF 立刻停表
if (done) { release(); streamController.close() }
else { streamController.enqueue(value) }
}

为什么这样就"只卡网线不卡慢消费者"? 注释(connectionConfig.ts:291-295)讲透了: 定时器只在等待网络读取时起表,chunk 一到就清;而一个"拿着流却不读"的慢消费者根本不会 触发 pull,也就不会起表——那种情况由服务端侧回收,不归这个定时器管。所以空闲超时 惩罚"服务端在流中途停止发送",正常的慢读者安然无恙。

流结束/取消/出错时,release() 会清表 + cleanup(),把连接池里的那条连接释放掉connectionConfig.ts:284-317)——这对连接池复用(§7)很重要,否则长连接会一直占着坑。

三个原语串起来就是长连接(命令执行、目录 watch)的完整生命周期:

setupRequestController ──握手──▶ clearStartTimeout ──▶ wrapStreamWithConnectionCleanup
│ (拆掉握手超时) │(装上空闲读超时)
握手超时 + 用户信号 只卡"网线哑掉",慢读不受伤
│ │
└──────────────── cleanup()(幂等,收尾释放连接)◀──────┘

7. 传输层:两套 fetch、连接池与探活

这节讲:上面的信号/头最终都灌进一个 fetch。envd 的 fetch 是特制的——带 HTTP/2、 连接池、代理,而且 REST 和 RPC 各用一套不同参数。

7.1 为什么要两套 fetch

http2.ts 导出 createEnvdFetch(REST 用,http2.ts:85-102)和 createEnvdRpcFetch (RPC 用,http2.ts:104-120)。两者底层都调 createEnvdFetchForRuntime,区别只在连接池大小:

fetch连接池默认在途上限默认覆盖环境变量
REST (createEnvdFetch)10(DEFAULT_ENVD_CONNECTION_LIMIT2000E2B_ENVD_INFLIGHT_REQUESTS
RPC (createEnvdRpcFetch)200(DEFAULT_ENVD_RPC_CONNECTION_LIMIT2000E2B_ENVD_RPC_CONNECTIONS / E2B_ENVD_RPC_INFLIGHT_REQUESTS

REST 只连一个 origin 做短请求,注释说(http2.ts:93-94)故意留少量连接、宁可牺牲单沙箱 吞吐也省连接压力;RPC 要撑大量并发命令/watch 流,所以连接池给到 200。

两个 fetch 都按 proxy 缓存envdFetchers / envdRpcFetchers 两个 Maphttp2.ts:20-21):无代理的请求共享一个 dispatcher,每个不同的代理 URL 各得一个。

7.2 HTTP/2 与代理

真正造 fetch 的是 buildEnvdFetcherhttp2.ts:45-83):仅在 Node 运行时用 undiciAgent/ProxyAgent,都开 allowH2: truehttp2.ts:62/66)拿 HTTP/2;有 proxy 就用 ProxyAgent。非 Node 运行时直接退回原生 fetchhttp2.ts:31-32)。外层再套一层 limitConcurrency 做在途请求限流。

7.3 REST 客户端与 /health 探活

EnvdApiClientenvd/api.ts:193-228)是个 openapi-fetch 客户端(createClient, envd/api.ts:210),baseUrl 就是 §3 推导出的 envdApiUrl。它有一个特别重要的方法配套: checkSandboxHealthenvd/api.ts:42-61)——GET /health,5 秒超时,返回三态:

// envd/api.ts:42 checkSandboxHealth(要点)
const res = await envdApi.api.GET('/health', { signal: AbortSignal.timeout(5000) })
if (res.response.status === 502) return false // 沙箱没了
if (res.response.ok) return true // 活着
return undefined // 说不清

handleEnvdApiErrorenvd/api.ts:100-141)把 REST 的 HTTP 码翻译成 SDK 错误类型,映射表 DEFAULT_ERROR_MAPenvd/api.ts:24-32):

HTTPSDK 错误
400InvalidArgumentError
401AuthenticationError
404NotFoundError
429RateLimitError
502formatSandboxTimeoutError(沙箱超时/没了)
507NotEnoughSpaceError

7.4 RPC 错误层与"断连消歧"(探活的用武之地)

RPC 侧的 envd/rpc.ts 把 Connect-RPC 的 Code(gRPC 状态码)映射到 SDK 错误,映射表 DEFAULT_ERROR_MAPenvd/rpc.ts:67-84):

Connect CodeSDK 错误备注
InvalidArgumentInvalidArgumentError
UnauthenticatedAuthenticationError
NotFoundNotFoundError
ResourceExhaustedRateLimitError
UnavailableformatSandboxTimeoutError沙箱不可达
CanceledTimeoutError多半是超过 requestTimeoutMs
DeadlineExceededTimeoutError多半是超过长连接 timeoutMs

真正精巧的是断连消歧。当连接在请求中途被掐断,不同 JS 运行时报的错文案不一样 (CONNECTION_TERMINATED_MESSAGESenvd/rpc.ts:32-36:Node 的 terminated、Bun、Deno 各一种), isConnectionTerminatedErrorenvd/rpc.ts:59-65)把这类"Code.Unknown + 断连文案"识别出来。

问题是:断连既可能是沙箱被杀了,也可能只是负载均衡器抖了一下handleRpcErrorWithHealthCheckenvd/rpc.ts:127-143)用 §7.3 的探活来区分:

// envd/rpc.ts:127 handleRpcErrorWithHealthCheck(要点)
if (isConnectionTerminatedError(err) && checkHealth) {
const running = await checkHealth().catch(() => undefined)
if (running === false) {
return new TimeoutError('...The sandbox was killed or reached its end of life...')
}
}
return handleRpcError(err, errorMap) // 否则按普通 RPC 错误处理(当作瞬时抖动)

也就是:断连了先去敲 /health 问一句"你还活着吗"——确认死了才报 TimeoutError (沙箱寿终),否则当瞬时网络问题走常规映射。REST 侧有对称的 handleEnvdApiFetchErrorenvd/api.ts:72-91)。这个 checkHealth 就是各子系统里 () => checkSandboxHealth(this.envdApi) (如 sandbox/filesystem/index.ts:377)。


8. 巧妙之处(可借鉴)

  • 门牌号可推导、不用查表。 URL 由 <port>-<id>.<domain> 纯拼接得出(connectionConfig.ts:451), 再靠 E2b-Sandbox-Id/Port 头让稳定入口 sandbox.<domain> 路由(sandbox/index.ts:159-162)—— 一个 443 入口覆盖所有沙箱的所有端口,无状态、无需注册中心查询。
  • 握手超时与运行超时解耦。 setupRequestControllerclearStartTimeoutconnectionConfig.ts:190-195)让"连不上要快速失败"与"连上后可长跑"两个矛盾诉求共存。
  • 空闲超时只惩罚哑网线。 wrapStreamWithConnectionCleanup 在读网络前起表、收到 chunk 即清表(connectionConfig.ts:290-305),慢消费者不触发 pull 就永不误伤——教科书级的 "只卡传输层、不卡应用层"。
  • 断连消歧靠主动探活。 不猜测断连原因,直接 /health 问一句 (envd/rpc.ts:132-139),把"沙箱被杀"和"网络抖动"分成两种错误——对上层重试策略很关键。
  • REST 与 RPC 连接池分治。 短 REST 10 条、长 RPC 200 条(http2.ts:22-23),并按 proxy 缓存 dispatcher(http2.ts:20-21),一个进程内多沙箱共享连接又互不挤占。

9. 边界与局限

  • 稳定入口 sandbox.<domain> 只保证生产可用。 自托管/非白名单域名回退到直连主机名 (connectionConfig.ts:427-433 的注释与 supportedDomains:6);浏览器因 CORS 也一律回退。
  • HTTP/2 连接池只在 Node 生效。 buildEnvdFetcher 只对 runtime === 'node' 用 undici 开 allowH2http2.ts:31-32),其它运行时退回原生 fetch,无自定义连接池。
  • 探活是尽力而为的三态。 /health 超时或异常一律返回 undefinedenvd/api.ts:57-60), 此时无法判定沙箱死活,断连会按普通错误处理——可能把"真死了"误当瞬时抖动。
  • accessToken 顶层字段已废弃。 代码仍读 E2B_ACCESS_TOKENconnectionConfig.ts:406), 但注释建议改走 apiHeaders;注意它与沙箱级的 X-Access-TokenenvdAccessToken)是两回事。

10. 横向对比(同组其它章)

想了解去哪章
E2B 是什么、两平面全景与阅读地图index.md
沙箱怎么被创建/暂停/杀死(控制面 REST)01-control-plane.md
连上 envd 之后怎么跑命令 / PTY03-commands-pty.md
连上 envd 之后怎么读写文件 / watch04-filesystem.md
沙箱镜像怎么构建出来05-templates.md
一份 spec 如何生成多语言 SDK06-spec-and-multilang.md

本章是 03/04 的共同底座:它们的每个流式 RPC / HTTP 调用都复用这里的 URL 推导、鉴权头、 超时信号与错误映射。


11. 代码地图(导航索引)

主题文件路径符号名
主机名推导(<port>-<id>.<domain>packages/js-sdk/src/connectionConfig.tsgetHost
稳定入口 vs 直连 URLpackages/js-sdk/src/connectionConfig.tsgetSandboxUrl / getSandboxDirectUrl
envd 固定端口常量packages/js-sdk/src/connectionConfig.tsConnectionConfig.envdPort(49983)
白名单域名 / 浏览器回退packages/js-sdk/src/connectionConfig.tssupportedDomains
opts/环境变量取值packages/js-sdk/src/connectionConfig.tsConnectionConfig 构造 + 静态 getter
取消/超时信号入口packages/js-sdk/src/connectionConfig.tsgetSignal / buildRequestSignal
握手 vs 运行超时分离packages/js-sdk/src/connectionConfig.tssetupRequestController
可重置空闲定时器packages/js-sdk/src/connectionConfig.tscreateIdleAbort
流清理 + 连接释放packages/js-sdk/src/connectionConfig.tswrapStreamWithConnectionCleanup
REST fetch(HTTP/2 + 连接池)packages/js-sdk/src/envd/http2.tscreateEnvdFetch / buildEnvdFetcher
RPC fetch(大连接池)packages/js-sdk/src/envd/http2.tscreateEnvdRpcFetch
REST 客户端packages/js-sdk/src/envd/api.tsEnvdApiClient
探活 /healthpackages/js-sdk/src/envd/api.tscheckSandboxHealth
REST HTTP 码 → SDK 错误packages/js-sdk/src/envd/api.tshandleEnvdApiError / DEFAULT_ERROR_MAP
RPC Code → SDK 错误packages/js-sdk/src/envd/rpc.tshandleRpcError / DEFAULT_ERROR_MAP
断连识别packages/js-sdk/src/envd/rpc.tsisConnectionTerminatedError
断连消歧(探活区分死/抖)packages/js-sdk/src/envd/rpc.tshandleRpcErrorWithHealthCheck
Basic 鉴权头(用户身份)packages/js-sdk/src/envd/rpc.tsauthenticationHeader
传输组装 + redirect 补丁 + 头注入packages/js-sdk/src/sandbox/index.ts构造函数(createConnectTransport, sandboxHeaders