@iasiv5/dsh-skip-browser-auth 0.3.11 → 0.4.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 +10 -5
- package/cordis.patch.yml +32 -10
- package/lib/adapters/carrier-neutral-021.js +85 -0
- package/lib/bridge.js +15 -1
- package/lib/client.js +1521 -0
- package/lib/compatibility.js +39 -0
- package/lib/gate.js +36 -19
- package/lib/host.js +35 -5
- package/lib/trusted-auth.js +32 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @iasiv5/dsh-skip-browser-auth
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@iasiv5/dsh-skip-browser-auth)
|
|
4
|
-
[](#兼容版本)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
7
|
个人自用的 DSH Web 插件:在已验证的 DSH compatibility profile 上自动跳过 BrowserAuth,打开 Web 地址即可直接使用,**不必每次把启动 URL 里那串随机 token 抄进浏览器**。
|
|
8
8
|
|
|
9
|
-
- **精确 profile 门控**:当前支持 `0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.
|
|
9
|
+
- **精确 profile 门控**:当前支持 `0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2` 与 `0.2.1-rc.1`,按 host/client 兼容代际分组;不是把所有版本塞进同一个实现。
|
|
10
10
|
- **严格成对匹配**:runtime `@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-client-connection` 必须组成已知版本对。混合版本、未知版本、profile 未实现或解析失败都会自动休眠。
|
|
11
|
-
- **分代适配**:`0.1.2-rc.1` 使用 `legacy-web-v1`;`0.1.5-rc.1` 与 `0.1.5-rc.2` 使用 `carrier-neutral-v2`,包含 request body mode、RPC carrier 和对应浏览器 client variant 适配;`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2` 使用 `carrier-neutral-v3`,覆盖 0.1.7 引入、0.2.0 沿用的 `admit()`/OperatorPeer 准入、`connection/request` waterfall 与 mount-preserving BrowserAuth
|
|
11
|
+
- **分代适配**:`0.1.2-rc.1` 使用 `legacy-web-v1`;`0.1.5-rc.1` 与 `0.1.5-rc.2` 使用 `carrier-neutral-v2`,包含 request body mode、RPC carrier 和对应浏览器 client variant 适配;`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2` 使用 `carrier-neutral-v3`,覆盖 0.1.7 引入、0.2.0 沿用的 `admit()`/OperatorPeer 准入、`connection/request` waterfall 与 mount-preserving BrowserAuth;`0.2.1-rc.1` 使用 `carrier-neutral-v4`,覆盖 0.2.1 的 Peer/principal 准入重构、`admission.peer` 透传 bridge、新 `/connect` 交换通道与 `webStartup` 信任来源(0.2.1 是真实契约变化,独立代际,见 docs/COMPATIBILITY.md §5.8/§5.9)。
|
|
12
12
|
- **不修改 DSH 本体与任何机制**:仅通过 DSH 官方插件 patch 机制替换 Connection 行;卸载或禁用插件并重启,即完全恢复官方行为。
|
|
13
13
|
- **安全提醒**:跳过认证后,Web 对本机/可信网络内的访问不再有身份层,请勿把 DSH 端口暴露给不可信网络。
|
|
14
14
|
|
|
@@ -37,6 +37,7 @@ sudo systemctl restart deepseek-harness.service # 按你的部署方式重启
|
|
|
37
37
|
- DSH 0.1.2-rc.1:legacy-web-v1
|
|
38
38
|
- DSH 0.1.5-rc.1 / 0.1.5-rc.2:carrier-neutral-v2
|
|
39
39
|
- DSH 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1 / 0.2.0-rc.2:carrier-neutral-v3
|
|
40
|
+
- DSH 0.2.1-rc.1:carrier-neutral-v4
|
|
40
41
|
其它版本或 runtime/connection 混合版本会自动休眠,不要强行处理。
|
|
41
42
|
desktop 宿主一律自动休眠(多进程树判别:安装槽位/启动器 argv/宿主二进制,0.3.9 起生效),这是设计行为,不要强行处理或改代码绕过;desktop 用户请直接使用 0.3.11+(0.3.6-0.3.10 在 desktop 有已知缺陷,见 docs/COMPATIBILITY.md §7.4)。
|
|
42
43
|
|
|
@@ -45,7 +46,7 @@ desktop 宿主一律自动休眠(多进程树判别:安装槽位/启动器 a
|
|
|
45
46
|
2. 重启 DSH Web 并轮询 http://127.0.0.1:3080 直到恢复 200
|
|
46
47
|
(systemd 系统级部署:sudo systemctl restart deepseek-harness.service;
|
|
47
48
|
user 级部署:systemctl --user restart dsh-web.service;端口以实际部署为准)。
|
|
48
|
-
3. 若版本是 0.1.2-rc.1、0.1.5-rc.1、0.1.5-rc.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1 或 0.2.
|
|
49
|
+
3. 若版本是 0.1.2-rc.1、0.1.5-rc.1、0.1.5-rc.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2 或 0.2.1-rc.1:
|
|
49
50
|
确认 journal/日志中出现固定警告
|
|
50
51
|
"BrowserAuth has been skipped",并验证浏览器直接打开 Web 地址不再要求 token。
|
|
51
52
|
4. 若版本不是精确白名单版本,或 profile/依赖契约不匹配:插件应自动休眠,
|
|
@@ -72,6 +73,7 @@ sudo systemctl restart deepseek-harness.service
|
|
|
72
73
|
| `0.1.7-rc.2` + `0.1.7-rc.2` | `carrier-neutral-v3` | 同上 | ✅ active(真实 npm fixture 组合测试 + 2026-09-26 实机 checklist 与回滚演练,见 docs/DESIGN.md) |
|
|
73
74
|
| `0.2.0-rc.1` + `0.2.0-rc.1` | `carrier-neutral-v3` | 同上(0.2.0-rc.1 为依赖对齐重发布:插件契约面代码逐字节一致) | ✅ active(npm diff 审计 + 真实 0.2.0-rc.1 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.6;2026-09-29 实机 checklist 已确认) |
|
|
74
75
|
| `0.2.0-rc.2` + `0.2.0-rc.2` | `carrier-neutral-v3` | 同上(0.2.0-rc.2 为依赖对齐重发布 + 无关 Desktop CLI 演进:插件契约面代码逐字节一致) | ✅ active(npm diff 审计 + 真实 0.2.0-rc.2 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.7;实机 checklist 见当日升级记录) |
|
|
76
|
+
| `0.2.1-rc.1` + `0.2.1-rc.1` | `carrier-neutral-v4` | carrier-neutral-021:Peer/principal 准入 + `admission.peer` 透传 bridge + `/connect` 交换通道 + webStartup 信任来源 + 0.2.1 browser client | ✅ active(§5.8 契约审计定谳新代际 + 真实 0.2.1-rc.1 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.9;实机 checklist 待宿主升级后回填) |
|
|
75
77
|
| desktop profile(存在 dsh-desktop-host 的环境,任意版本对) | — | 😴 dormant-by-design:desktop 一律休眠 | dormant |
|
|
76
78
|
| 其它版本或混合版本对 | — | 😴 自动休眠:官方行为分毫不变 | dormant |
|
|
77
79
|
|
|
@@ -92,6 +94,9 @@ runtime=0.2.0-rc.1 + connection=0.2.0-rc.1 → carrier-neutral-v3
|
|
|
92
94
|
runtime=0.2.0-rc.1 + connection=0.2.0-rc.2 → dormant(同代际跨 patch 混合同样是事故形态)
|
|
93
95
|
runtime=0.2.0-rc.2 + connection=0.2.0-rc.1 → dormant
|
|
94
96
|
runtime=0.2.0-rc.2 + connection=0.2.0-rc.2 → carrier-neutral-v3
|
|
97
|
+
runtime=0.2.0-rc.2 + connection=0.2.1-rc.1 → dormant(跨代际混合:0.2.1 是真实契约变化)
|
|
98
|
+
runtime=0.2.1-rc.1 + connection=0.2.0-rc.2 → dormant
|
|
99
|
+
runtime=0.2.1-rc.1 + connection=0.2.1-rc.1 → carrier-neutral-v4
|
|
95
100
|
runtime=0.1.7-rc.2 + connection=0.1.5-rc.2 → dormant(跨代际混合)
|
|
96
101
|
```
|
|
97
102
|
|
|
@@ -105,7 +110,7 @@ runtime=0.1.7-rc.2 + connection=0.1.5-rc.2 → dormant(跨代际混合)
|
|
|
105
110
|
- `legacy-web-v1` 保留 0.1.2 的 `/api` prefix 与 buffered bridge。
|
|
106
111
|
- `carrier-neutral-v2` 使用 runtime loader 取得当前 0.1.5 connection module,支持 `requestBodyMode`、exact Fetch route 和 streaming body,并将 generic `connection.rpc.handle()` 绑定到拥有 Web carrier 的 replacement context,避免 0.1.5 的 405 回归。
|
|
107
112
|
- `carrier-neutral-v3` 在 v2 契约之上复刻 0.1.7 的三处新语义:`/api` 准入改走 `admit()`(OperatorPeer 作为 RPC handler 第 4 参传入)、共享通道请求经 `connection/request` waterfall(监听插件继续可见)、BrowserAuth stub 使用 mount-preserving URL 语义(token 清理 303 `./`、启动 URL 保留 mount)。
|
|
108
|
-
- client bundle 是显式 profile dispatcher:host 通过 `webserver/index-inject` 注入 profile id,0.1.2/0.1.5/0.1.7 代际使用各自官方 factory(`0.2.0-rc.1`/`0.2.0-rc.2` 与 `0.1.7` 契约逐字节一致,共用 0.1.7 factory
|
|
113
|
+
- client bundle 是显式 profile dispatcher:host 通过 `webserver/index-inject` 注入 profile id,0.1.2/0.1.5/0.1.7 代际使用各自官方 factory(`0.2.0-rc.1`/`0.2.0-rc.2` 与 `0.1.7` 契约逐字节一致,共用 0.1.7 factory);`0.2.1-rc.1` 使用 rc21 variant(锚定官方 browser 入口 `lib/client.browser.js`);profile 缺失或未知时不猜测、不加载错误 variant。
|
|
109
114
|
- 激活时进程逐字输出固定警告:
|
|
110
115
|
|
|
111
116
|
```text
|
package/cordis.patch.yml
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
# 自门控 bundle patch:第一行把官方 connection 行的 disabled 绑定到版本探针
|
|
3
3
|
# (双锚点:宿主 runtime 本体 @deepseek-ai/dsh 与官方 connection 包的 pnpm
|
|
4
4
|
# runtime/connection 必须命中同一个 active compatibility profile;runtime 锚点
|
|
5
|
-
# 只可能来自 runtime fallback
|
|
6
|
-
# trusted-connection
|
|
5
|
+
# 只可能来自 runtime fallback,不会被插件自身依赖污染);随后按代际插入两行
|
|
6
|
+
# 插件行(≤0.2.0 = trusted-connection/webRuntime;0.2.1+ =
|
|
7
|
+
# trusted-connection-021/webStartup;id 集合互斥,任何宿主至多激活一行),
|
|
8
|
+
# 各行 config 携带 profile id,disabled 为行绑定表达式的否定。
|
|
7
9
|
# 任何门控条件不成立(锚点解析失败、版本漂移、行不存在、名字漂移、
|
|
8
10
|
# 官方行未禁用),Replacement 一律保持 dormant(fail-closed)。探针为假时,
|
|
9
11
|
# 即使后续 patch 层把官方行强制 disabled,行绑定内嵌探针仍保持 dormant。
|
|
@@ -18,7 +20,8 @@
|
|
|
18
20
|
arg.indexOf("dsh-desktop-host") !== -1); } catch (sigError2) { } let webByBinary = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && typeof hostProcess.execPath === 'string') { const execBase = hostProcess.execPath.slice(Math.max(hostProcess.execPath.lastIndexOf('/'), hostProcess.execPath.lastIndexOf('\\')) + 1).toLowerCase(); webByBinary = execBase === 'node' || execBase === 'node.exe' || execBase === 'nodejs' || execBase === 'nodejs.exe'; } } catch (sigError3) { } let desktopByPackage = false; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; desktopByPackage = typeof desktopUrl === 'string'; } catch (sigError4) { } hostProfile = desktopByArgv || desktopBySlot || desktopByPackage ||
|
|
19
21
|
!webByBinary ? 'desktop' : 'web'; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors =
|
|
20
22
|
[["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
|
|
21
|
-
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null &&
|
|
23
|
+
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v4","pairs":[{"runtime":"0.2.1-rc.1","connection":"0.2.1-rc.1"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null &&
|
|
24
|
+
["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3","carrier-neutral-v4"].includes(profile); } catch (gateError) { return false; } })()
|
|
22
25
|
- insert:
|
|
23
26
|
- id: trusted-connection
|
|
24
27
|
name: '@iasiv5/dsh-skip-browser-auth'
|
|
@@ -31,11 +34,30 @@
|
|
|
31
34
|
(sigError2) { } let webByBinary = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && typeof hostProcess.execPath === 'string') { const execBase = hostProcess.execPath.slice(Math.max(hostProcess.execPath.lastIndexOf('/'), hostProcess.execPath.lastIndexOf('\\')) + 1).toLowerCase(); webByBinary = execBase === 'node' || execBase === 'node.exe' || execBase === 'nodejs' || execBase === 'nodejs.exe'; } } catch (sigError3) { } let desktopByPackage = false; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; desktopByPackage = typeof desktopUrl === 'string'; } catch (sigError4) { } hostProfile = desktopByArgv || desktopBySlot || desktopByPackage || !webByBinary ? 'desktop' : 'web'; const
|
|
32
35
|
versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors =
|
|
33
36
|
[["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
|
|
34
|
-
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()
|
|
37
|
+
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v4","pairs":[{"runtime":"0.2.1-rc.1","connection":"0.2.1-rc.1"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()
|
|
35
38
|
disabled: !!js >-
|
|
36
|
-
!((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
!((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile = 'web'; let hostProcess = null; try { if (typeof process === 'object' && process !== null) hostProcess = process; } catch (sigError0) { } let slot = ''; let desktopBySlot = false; try { const decodedBase = decodeURIComponent(typeof ctx.baseUrl === 'string' ? ctx.baseUrl : ''); const trimmedBase = decodedBase.endsWith('/') ? decodedBase.slice(0, -1) : decodedBase; slot =
|
|
40
|
+
trimmedBase.slice(trimmedBase.lastIndexOf('/') + 1).toLowerCase(); desktopBySlot = slot === "desktop"; } catch (sigError1) { } let desktopByArgv = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && Array.isArray(hostProcess.argv)) desktopByArgv = hostProcess.argv.some((arg) => typeof arg === 'string' && arg.indexOf("dsh-desktop-host") !== -1); } catch (sigError2) { } let webByBinary = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && typeof hostProcess.execPath === 'string') { const execBase = hostProcess.execPath.slice(Math.max(hostProcess.execPath.lastIndexOf('/'), hostProcess.execPath.lastIndexOf('\\')) + 1).toLowerCase(); webByBinary = execBase === 'node' || execBase === 'node.exe' || execBase === 'nodejs' || execBase === 'nodejs.exe'; } } catch (sigError3) { } let desktopByPackage = false; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes:
|
|
41
|
+
{} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; desktopByPackage = typeof desktopUrl === 'string'; } catch (sigError4) { } hostProfile = desktopByArgv || desktopBySlot || desktopByPackage || !webByBinary ? 'desktop' : 'web'; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule ||
|
|
42
|
+
typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
|
|
43
|
+
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v4","pairs":[{"runtime":"0.2.1-rc.1","connection":"0.2.1-rc.1"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); if (profile === null ||
|
|
44
|
+
!["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3"].includes(profile)) return false; return official.disabled === true; } catch (gateError) { return false; } })())
|
|
45
|
+
- id: trusted-connection-021
|
|
46
|
+
name: '@iasiv5/dsh-skip-browser-auth'
|
|
47
|
+
inject:
|
|
48
|
+
- webStartup
|
|
49
|
+
config:
|
|
50
|
+
trustedHosts: !!js ctx.webStartup.trustedHosts
|
|
51
|
+
compatibilityProfile: !!js >-
|
|
52
|
+
(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile = 'web'; let hostProcess = null; try { if (typeof process === 'object' && process !== null) hostProcess = process; } catch (sigError0) { } let slot = ''; let desktopBySlot = false; try { const decodedBase = decodeURIComponent(typeof ctx.baseUrl === 'string' ? ctx.baseUrl : ''); const trimmedBase = decodedBase.endsWith('/') ? decodedBase.slice(0, -1) : decodedBase; slot = trimmedBase.slice(trimmedBase.lastIndexOf('/') + 1).toLowerCase(); desktopBySlot = slot === "desktop"; } catch (sigError1) { } let desktopByArgv = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && Array.isArray(hostProcess.argv)) desktopByArgv = hostProcess.argv.some((arg) => typeof arg === 'string' && arg.indexOf("dsh-desktop-host") !== -1); } catch
|
|
53
|
+
(sigError2) { } let webByBinary = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && typeof hostProcess.execPath === 'string') { const execBase = hostProcess.execPath.slice(Math.max(hostProcess.execPath.lastIndexOf('/'), hostProcess.execPath.lastIndexOf('\\')) + 1).toLowerCase(); webByBinary = execBase === 'node' || execBase === 'node.exe' || execBase === 'nodejs' || execBase === 'nodejs.exe'; } } catch (sigError3) { } let desktopByPackage = false; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; desktopByPackage = typeof desktopUrl === 'string'; } catch (sigError4) { } hostProfile = desktopByArgv || desktopBySlot || desktopByPackage || !webByBinary ? 'desktop' : 'web'; const
|
|
54
|
+
versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors =
|
|
55
|
+
[["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
|
|
56
|
+
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v4","pairs":[{"runtime":"0.2.1-rc.1","connection":"0.2.1-rc.1"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()
|
|
57
|
+
disabled: !!js >-
|
|
58
|
+
!((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile = 'web'; let hostProcess = null; try { if (typeof process === 'object' && process !== null) hostProcess = process; } catch (sigError0) { } let slot = ''; let desktopBySlot = false; try { const decodedBase = decodeURIComponent(typeof ctx.baseUrl === 'string' ? ctx.baseUrl : ''); const trimmedBase = decodedBase.endsWith('/') ? decodedBase.slice(0, -1) : decodedBase; slot =
|
|
59
|
+
trimmedBase.slice(trimmedBase.lastIndexOf('/') + 1).toLowerCase(); desktopBySlot = slot === "desktop"; } catch (sigError1) { } let desktopByArgv = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && Array.isArray(hostProcess.argv)) desktopByArgv = hostProcess.argv.some((arg) => typeof arg === 'string' && arg.indexOf("dsh-desktop-host") !== -1); } catch (sigError2) { } let webByBinary = false; try { if (hostProcess !== null && typeof hostProcess === 'object' && typeof hostProcess.execPath === 'string') { const execBase = hostProcess.execPath.slice(Math.max(hostProcess.execPath.lastIndexOf('/'), hostProcess.execPath.lastIndexOf('\\')) + 1).toLowerCase(); webByBinary = execBase === 'node' || execBase === 'node.exe' || execBase === 'nodejs' || execBase === 'nodejs.exe'; } } catch (sigError3) { } let desktopByPackage = false; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes:
|
|
60
|
+
{} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; desktopByPackage = typeof desktopUrl === 'string'; } catch (sigError4) { } hostProfile = desktopByArgv || desktopBySlot || desktopByPackage || !webByBinary ? 'desktop' : 'web'; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule ||
|
|
61
|
+
typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
|
|
62
|
+
[{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v4","pairs":[{"runtime":"0.2.1-rc.1","connection":"0.2.1-rc.1"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); if (profile === null || !["carrier-neutral-v4"].includes(profile)) return false;
|
|
63
|
+
return official.disabled === true; } catch (gateError) { return false; } })())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { bridgeWithPeer } from '../bridge.js';
|
|
2
|
+
/**
|
|
3
|
+
* Host implementation for the 0.2.1 carrier-neutral connection contract.
|
|
4
|
+
*
|
|
5
|
+
* 相对 carrier-neutral-017(0.1.7–0.2.0 代际)的官方差异,均来自 0.2.1-rc.1
|
|
6
|
+
* 官方 apply 的逐行对照(docs/COMPATIBILITY.md §5.8 审计 + §5.9 收编):
|
|
7
|
+
* 1. 共享 `/api` 路由的 bridge 调用把 admit() 产生的 Peer 透传给 fetch
|
|
8
|
+
* (`bridge(req, res, fetchHandler, admission.peer, maxRequestBodyBytes)`)
|
|
9
|
+
* ——Peer 最终成为 RPC handler 第 4 参,并把 exact Fetch route 门在
|
|
10
|
+
* owner-principal 上(非 owner ⇒ 403,官方 fetch handler 自带);
|
|
11
|
+
* 2. 新增 exact `/connect` 路由(`module.CONNECT_PATH`),handler 直接委托
|
|
12
|
+
* `connection.authorizeConnect(req, res)`(socket 连接协议通道);
|
|
13
|
+
* 3. `admit()` 语义升级(isTrusted → disposed → browserAuth.isAuthenticated ⇒
|
|
14
|
+
* browserPeer / token 台账 peer),但形状仍是 `{peer} | {rejection}`,
|
|
15
|
+
* Replacement 的消费方式不变——认证由 trusted-auth stub 承担(恒
|
|
16
|
+
* authenticated ⇒ 恒 browserPeer,principal 为 owner,与官方浏览器半区
|
|
17
|
+
* 同一对象),fence 语义由 stub 与 service 共同保持;
|
|
18
|
+
* 4. `register()` 仍是 owner-based 形状(sibling 无 webServer 的 405 语义
|
|
19
|
+
* 不变),沿用 017 的 carrier 绑定包装。
|
|
20
|
+
*
|
|
21
|
+
* 代际 seam 守卫:`CONNECT_PATH` 导出 + `authorizeConnect` 方法是 0.2.1 特有
|
|
22
|
+
* 面——更早代际的模块(含 0.1.7/0.2.0)在这里以确定性报错被拒绝(0.1.7 的
|
|
23
|
+
* `admit()` 存在性已不足以区分代际)。
|
|
24
|
+
*/
|
|
25
|
+
export const HOST_ADAPTER_ID = 'carrier-neutral-021';
|
|
26
|
+
export function createCarrierNeutral021Runtime(input) {
|
|
27
|
+
const connection = new input.module.HostConnectionService(input.ctx, input.trustedHosts, input.auth);
|
|
28
|
+
const fetchHandler = connection.createSharedFetchHandler(input.module.API_PATH);
|
|
29
|
+
// 代际守卫(fail loud 于 apply 前,绝不静默降级到错误代际的语义):
|
|
30
|
+
// - admit():0.1.7 起的准入 seam(0.2.1 延续);
|
|
31
|
+
// - CONNECT_PATH + authorizeConnect:0.2.1 特有的 /connect 通道 seam;
|
|
32
|
+
// - requestBodyMode:0.1.5 起的 body-mode 感知 handler。
|
|
33
|
+
if (typeof connection.admit !== 'function') {
|
|
34
|
+
throw new Error('@iasiv5/dsh-skip-browser-auth: carrier-neutral-021 profile requires the 0.2.1 admit() connection seam');
|
|
35
|
+
}
|
|
36
|
+
if (typeof connection.authorizeConnect !== 'function' || input.module.CONNECT_PATH === undefined) {
|
|
37
|
+
throw new Error('@iasiv5/dsh-skip-browser-auth: carrier-neutral-021 profile requires the 0.2.1 /connect connection seam (CONNECT_PATH + authorizeConnect)');
|
|
38
|
+
}
|
|
39
|
+
if (typeof fetchHandler.requestBodyMode !== 'function') {
|
|
40
|
+
throw new Error('@iasiv5/dsh-skip-browser-auth: carrier-neutral-021 profile requires a requestBodyMode-aware connection handler');
|
|
41
|
+
}
|
|
42
|
+
const waterfall = input.ctx.waterfall;
|
|
43
|
+
if (typeof waterfall !== 'function') {
|
|
44
|
+
throw new Error('@iasiv5/dsh-skip-browser-auth: carrier-neutral-021 profile requires a waterfall-capable carrier context');
|
|
45
|
+
}
|
|
46
|
+
// 0.2.1 keeps the owner-based register() shape: the physical route mounts on
|
|
47
|
+
// `owner.webServer`, while rpc.handle() readings often come from sibling
|
|
48
|
+
// plugin contexts without webServer. Bind only this replacement instance's
|
|
49
|
+
// registrations to the adapter's webServer-owning context (the 405 fix,
|
|
50
|
+
// unchanged from the 017 adapter).
|
|
51
|
+
const service = connection;
|
|
52
|
+
const originalRegister = service.register;
|
|
53
|
+
if (typeof originalRegister !== 'function') {
|
|
54
|
+
throw new Error('@iasiv5/dsh-skip-browser-auth: carrier-neutral-021 connection lacks its RPC register seam');
|
|
55
|
+
}
|
|
56
|
+
service.register = function registerWithCarrier(_owner, channel, handler) {
|
|
57
|
+
return originalRegister.call(this, input.ctx, channel, handler);
|
|
58
|
+
};
|
|
59
|
+
const admitted = connection;
|
|
60
|
+
const connectPath = input.module.CONNECT_PATH;
|
|
61
|
+
const route = {
|
|
62
|
+
kind: 'prefix',
|
|
63
|
+
path: input.module.API_PATH,
|
|
64
|
+
handler: async (req, res) => {
|
|
65
|
+
const admission = admitted.admit(req);
|
|
66
|
+
if ('rejection' in admission) {
|
|
67
|
+
res.writeHead(admission.rejection);
|
|
68
|
+
res.end(admission.rejection === 401 ? 'unauthorized' : 'forbidden');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await waterfall.call(input.ctx, 'connection/request', req, res, () => bridgeWithPeer(req, res, fetchHandler, admission.peer, input.maxRequestBodyBytes));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
// 官方 0.2.1 apply 的第二个注册(顺序照抄:/api 在前,/connect 在后):
|
|
75
|
+
// exact CONNECT_PATH 路由整体委托 authorizeConnect( admission/栅栏语义在
|
|
76
|
+
// service 内部,Replacement 不复刻其实现)。
|
|
77
|
+
const connectRoute = {
|
|
78
|
+
kind: 'exact',
|
|
79
|
+
path: connectPath,
|
|
80
|
+
handler: (req, res) => {
|
|
81
|
+
admitted.authorizeConnect(req, res);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return { connection, route, additionalRoutes: [connectRoute], fetchHandler, adapterId: HOST_ADAPTER_ID };
|
|
85
|
+
}
|
package/lib/bridge.js
CHANGED
|
@@ -19,6 +19,20 @@ export const DEFAULT_MAX_REQUEST_BODY_BYTES = 300 * 1024 * 1024;
|
|
|
19
19
|
* @param maxRequestBodyBytes - maximum body bytes buffered before dispatch.
|
|
20
20
|
*/
|
|
21
21
|
export async function bridge(req, res, apiHandler, maxRequestBodyBytes = DEFAULT_MAX_REQUEST_BODY_BYTES) {
|
|
22
|
+
return bridgeRequest(req, res, apiHandler, undefined, maxRequestBodyBytes);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 0.2.1 (carrier-neutral-021) bridge: identical to {@link bridge} except the
|
|
26
|
+
* admission Peer is threaded into the handler call, replicating the official
|
|
27
|
+
* `bridge(req, res, fetchHandler, admission.peer, maxRequestBodyBytes)` wiring
|
|
28
|
+
* (the peer lands in RPC handler slot 4 and gates exact Fetch routes to
|
|
29
|
+
* owner-principal peers). Generations never cross: the v3 adapter keeps
|
|
30
|
+
* calling {@link bridge}.
|
|
31
|
+
*/
|
|
32
|
+
export async function bridgeWithPeer(req, res, apiHandler, peer, maxRequestBodyBytes = DEFAULT_MAX_REQUEST_BODY_BYTES) {
|
|
33
|
+
return bridgeRequest(req, res, apiHandler, peer, maxRequestBodyBytes);
|
|
34
|
+
}
|
|
35
|
+
async function bridgeRequest(req, res, apiHandler, peer, maxRequestBodyBytes) {
|
|
22
36
|
const abort = new AbortController();
|
|
23
37
|
// Client-disconnect detection MUST hang off the response, not the request:
|
|
24
38
|
// since Node 16, IncomingMessage 'close' fires as soon as the request body is
|
|
@@ -73,7 +87,7 @@ export async function bridge(req, res, apiHandler, maxRequestBodyBytes = DEFAULT
|
|
|
73
87
|
duplex: 'half',
|
|
74
88
|
});
|
|
75
89
|
}
|
|
76
|
-
const response = await apiHandler.fetch(request);
|
|
90
|
+
const response = await apiHandler.fetch(request, peer);
|
|
77
91
|
const requestUnread = bodyMode === 'streaming' && !req.readableEnded;
|
|
78
92
|
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
79
93
|
res.writeHead(response.status, requestUnread
|