dsh-deepseek-web-login 0.6.23 → 0.6.24

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/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  本项目大致遵循语义化版本;日期为本地时间。
4
4
 
5
+ ## 0.6.24 — 2026-10-01
6
+
7
+ **修复:浏览器代理传输层不支持 `FormData` ⇒ 图片全部上传失败。**
8
+
9
+ ### 现象
10
+
11
+ 切到浏览器代理传输层(0.6.20 起,官方 DSH 桌面端拿不到 `electron.net.fetch` 时的默认路径)后,
12
+ 粘贴图片会得到:
13
+
14
+ ```
15
+ 有 1 张图片没能传给模型(DeepSeek 图片上传失败:浏览器代理传输层暂不支持
16
+ Blob/FormData/ReadableStream 请求体),本轮回答只基于文字内容。
17
+ ```
18
+
19
+ ### 根因
20
+
21
+ 0.6.20 的 `bodyToPageInit()` 只把 `string` / `Uint8Array` / `ArrayBuffer` 转成页面侧可重建的表达式,
22
+ 遇到别的类型直接抛错。而**图片上传走的正是 `FormData`(multipart)**
23
+ (`webapi.uploadImageFile` 里 `form.append('file', new Blob(...), name)`)。
24
+
25
+ ### 修法
26
+
27
+ `bodyToPageInit()` 现在支持 `FormData` 与 `Blob`,做法是把内容按**字节**搬到页面里重建:
28
+
29
+ - 字节用 **base64** 传(而非 JSON 数组)—— 体积约为 1/2.4,CDP 消息不会被大图撑爆;
30
+ - 页面侧注入两个小工具:`__dshB64ToBytes`(base64 → Uint8Array)与
31
+ `__dshRebuildFormData`(按 parts 重建 `FormData`);
32
+ - 文件部分用 **`new File(...)`** 而不是 `new Blob(...)` —— 必须保住 **filename**,
33
+ 因为服务端按**文件名后缀**判图片类型(见 `protocol.ts`);
34
+ - 语言无关地遍历 `FormData`(用 `forEach`,不用 `entries()` —— 后者要 `DOM.Iterable` lib)。
35
+
36
+ ### 验证
37
+
38
+ - 新增一条**真实往返**用例:启本地 HTTP 服务,用 200 KB 的假 PNG 走 `FormData` 上传,
39
+ 断言 `content-type` 带 boundary、`filename` 保住、PNG 魔数(8 字节)原样到达
40
+ - 变异反向验证:把 `FormData` 分支禁用 ⇒ 用例变红、`rc=1`,报错信息与用户截图**逐字一致**
41
+ - `tsc --noEmit`、`npm run test`(60/60)、`check-smoke`、`check-bundle` 全通过
42
+
5
43
  ## 0.6.23 — 2026-10-01
6
44
 
7
45
  **一个窗口 = 一条对话线:不再断链、不再分叉、不再删会话。**
package/lib/index.js CHANGED
@@ -9038,12 +9038,78 @@ async function shutdownBrowserTransport() {
9038
9038
  activeSession = void 0;
9039
9039
  launchPromise = void 0;
9040
9040
  }
9041
- function bodyToPageInit(body) {
9041
+ /** 页面上下文里的辅助函数名(把 base64 还原成字节)。 */
9042
+ const B64_HELPER = "__dshB64ToBytes";
9043
+ /** 页面上下文里的辅助函数名(把序列化过的 parts 重建成 FormData)。 */
9044
+ const FORMDATA_HELPER = "__dshRebuildFormData";
9045
+ /** 页面里用到的两个小工具(随每次 evaluate 一起注入,保持无状态)。 */
9046
+ const PAGE_HELPERS = `
9047
+ const ${B64_HELPER} = (b64) => {
9048
+ const bin = atob(b64);
9049
+ const out = new Uint8Array(bin.length);
9050
+ for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
9051
+ return out;
9052
+ };
9053
+ const ${FORMDATA_HELPER} = (parts) => {
9054
+ const form = new FormData();
9055
+ for (const p of parts) {
9056
+ if (p.t === 'text') form.append(p.name, p.value);
9057
+ else form.append(p.name, new File([${B64_HELPER}(p.b64)], p.filename, { type: p.type }));
9058
+ }
9059
+ return form;
9060
+ };
9061
+ `;
9062
+ /**
9063
+ * 把 Node 侧的请求体转成"能在页面里重建它"的表达式。
9064
+ *
9065
+ * 🔴 0.6.20 的第一版只支持 string / Uint8Array / ArrayBuffer,遇到 FormData 直接抛
9066
+ * "暂不支持" —— 而**图片上传走的就是 FormData(multipart)**(见 `webapi.uploadImageFile`),
9067
+ * 于是"切到浏览器代理传输层之后图片全传不上去"(2026-10-01 用户报,报错文案就来自这里)。
9068
+ *
9069
+ * 0.6.24:FormData / Blob 都按**字节**搬过去,在页面里重建。
9070
+ * 细节:
9071
+ * - 用 **base64** 而不是 JSON 数组传字节(体积约为 1/2.4,CDP 消息不至于被图片撑爆);
9072
+ * - 用 `new File(...)` 而不是 `new Blob(...)` —— 必须保住 **filename**,
9073
+ * 服务端是按**文件名后缀**判图片类型的(见 protocol.ts 的说明);
9074
+ * - 语言无关地遍历 FormData 的 entry:`TextPart` 原样作为字段,其余一律当文件。
9075
+ */
9076
+ async function bodyToPageInit(body) {
9042
9077
  if (body === void 0 || body === null) return "undefined";
9043
9078
  if (typeof body === "string") return JSON.stringify(body);
9044
- if (body instanceof Uint8Array) return `new Uint8Array(${JSON.stringify(Array.from(body))})`;
9045
- if (body instanceof ArrayBuffer) return `new Uint8Array(${JSON.stringify(Array.from(new Uint8Array(body)))})`;
9046
- throw new Error("浏览器代理传输层暂不支持 Blob/FormData/ReadableStream 请求体");
9079
+ if (body instanceof Uint8Array) return `${B64_HELPER}(${JSON.stringify(Buffer.from(body).toString("base64"))})`;
9080
+ if (body instanceof ArrayBuffer) return `${B64_HELPER}(${JSON.stringify(Buffer.from(new Uint8Array(body)).toString("base64"))})`;
9081
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
9082
+ const raw = [];
9083
+ body.forEach((value, name) => {
9084
+ raw.push([name, value]);
9085
+ });
9086
+ const parts = [];
9087
+ for (const [name, value] of raw) {
9088
+ if (typeof value === "string") {
9089
+ parts.push({
9090
+ t: "text",
9091
+ name,
9092
+ value
9093
+ });
9094
+ continue;
9095
+ }
9096
+ const blob = value;
9097
+ const b64 = Buffer.from(new Uint8Array(await blob.arrayBuffer())).toString("base64");
9098
+ parts.push({
9099
+ t: "file",
9100
+ name,
9101
+ filename: blob.name || name || "blob",
9102
+ type: blob.type || "application/octet-stream",
9103
+ b64
9104
+ });
9105
+ }
9106
+ return `${FORMDATA_HELPER}(${JSON.stringify(parts)})`;
9107
+ }
9108
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
9109
+ const b64 = Buffer.from(new Uint8Array(await body.arrayBuffer())).toString("base64");
9110
+ return `new Blob([${B64_HELPER}(${JSON.stringify(b64)})], { type: ${JSON.stringify(body.type || "application/octet-stream")} })`;
9111
+ }
9112
+ throw new Error("浏览器代理传输层不支持的请求体类型(支持 string / Uint8Array / ArrayBuffer / FormData / Blob)");
9047
9113
  }
9048
9114
  function createBrowserFetch() {
9049
9115
  return async (input, init) => {
@@ -9058,7 +9124,7 @@ function createBrowserFetch() {
9058
9124
  else if (Array.isArray(init.headers)) for (const [key, value] of init.headers) headers[key] = value;
9059
9125
  else for (const [key, value] of Object.entries(init.headers)) headers[key] = String(value);
9060
9126
  }
9061
- const bodyExpr = bodyToPageInit(init?.body);
9127
+ const bodyExpr = await bodyToPageInit(init?.body);
9062
9128
  const requestId = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
9063
9129
  const headersPromise = new Promise((resolve, reject) => {
9064
9130
  const state = {
@@ -9084,6 +9150,7 @@ function createBrowserFetch() {
9084
9150
  });
9085
9151
  const expression = `
9086
9152
  (async () => {
9153
+ ${PAGE_HELPERS}
9087
9154
  const requestId = ${JSON.stringify(requestId)};
9088
9155
  try {
9089
9156
  const init = {
@@ -10010,7 +10077,7 @@ async function checkForUpdate(current, fetchImpl) {
10010
10077
  * 兜底常量与 package.json 的一致性由 `tests/check-smoke.mjs` 守着,不会漂。
10011
10078
  */
10012
10079
  /** 与 package.json 保持一致的兜底版本(由测试保证不会漂)。 */
10013
- const FALLBACK_VERSION = "0.6.23";
10080
+ const FALLBACK_VERSION = "0.6.24";
10014
10081
  let cached;
10015
10082
  /** 本插件版本(如 `0.1.26`)。 */
10016
10083
  function pluginVersion() {