// 游戏分发 E2E 脚本共用的 multipart 发布 helper。 // // 这些脚本都直连 api-server,发布口是同一套契约:`metadata` JSON 文本 part + 可选 `cover` // 二进制 part + 按顺序重复的 `screenshot` 二进制 part;各自的 `api()` 也只在 body 形态上分支。 // 这里收敛这段稳定契约,脚本只保留自己的路径、鉴权头与返回形状差异。 /** 1x1 透明 PNG:仅用于满足发布必填的封面/截图 part,内容不参与断言。 */ const COVER_PNG = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64', ); /** * 发布用图片 part。 * * 默认文件名 `-.png`、`image/png`;脚本可通过 `options.fileName` 保留自己的前缀, * 或用 `options.bytes` / `options.contentType` 覆盖字节与类型。 */ export function imagePart(kind, id, options = {}) { const { bytes = COVER_PNG, fileName = `${kind}-${id}.png`, contentType = 'image/png', } = options; return { bytes, fileName, contentType }; } /** * 组装发布请求体:`metadata` 文本 part + 可选 `cover` / 按顺序重复的 `screenshot` 二进制 part。 * * `metadata.screenshots` 里的 `null` 槽位按出现顺序消费 `screenshots` 数组里的 part;string * 槽位沿用该 objectKey。调用方负责保证 null 槽位数量与 part 数量一致(不一致服务端会 400)。 */ export function publishFormData(metadata, { cover, screenshots = [] } = {}) { const form = new FormData(); form.append('metadata', JSON.stringify(metadata)); if (cover) { form.append( 'cover', new Blob([cover.bytes], { type: cover.contentType }), cover.fileName, ); } for (const screenshot of screenshots) { if (!screenshot) continue; form.append( 'screenshot', new Blob([screenshot.bytes], { type: screenshot.contentType }), screenshot.fileName, ); } return form; } /** * 推导 `api()` 的最终请求头与请求体。 * * - `formData`:multipart/form-data,不手动设 `Content-Type`,交给 fetch 生成 boundary。 * - `binary`:直接作为 body(例如分片 PUT 的字节)。 * - `body !== undefined`:按 JSON 序列化,并补 `Content-Type: application/json`。 * * 调用方先自行合并 `{ ...ENVELOPE, ...headers }` 与 `Authorization`,再把结果作为 `headers` * 传进来;返回值里的 `headers` 是补完 `Content-Type` 后的副本。 */ export function resolveApiRequest({ formData, binary, body, headers = {}, } = {}) { const finalHeaders = { ...headers }; let finalBody; if (formData) { finalBody = formData; } else if (binary) { finalBody = binary; } else if (body !== undefined) { finalHeaders['Content-Type'] = 'application/json'; finalBody = JSON.stringify(body); } return { headers: finalHeaders, body: finalBody }; }