可靠的本地投递

目标读者:验证离线、重启和附件部分结果的开发者。

Godot SDK 只使用一套有界本地队列。启用队列后,首次 HTTP 请求之前先原子保存冻结的报告。磁盘或容量错误会停止本次提交,不能称作“已排队”。显式本地 offline_queue: false 或已验证的项目设置 false 会禁止新反馈落盘;此时截图只放在内存中,进程退出后无法恢复这次提交。

最小示例

EchoNebulaFeedback.configure({
  "project_key": "enb_pk_xxx",
  "public_api_base_url": "http://localhost:4000",
  "offline_queue": true,
  "max_offline_queue_items": 100,
  "max_offline_queue_attempts": 3,
  "max_offline_queue_bytes": 67108864
})

默认最多 100 条待发送记录、100 条死信,受管理记录与附件合计 64 MiB,每份报告或附件最多尝试 3 次。本地设置的硬上限是每类 100 条、每阶段 10 次、256 MiB;字节预算最低 8 KiB。队列会为原子进度保存预留空间。容量已满时拒绝新提交,不挤掉正在等待的报告;较旧死信可以被清理。将字节预算降低到现有用量以下,可能暂停投递,直到恢复空间或预算。

确认与重启

提交入口在截图等待之前锁定一次动作,并冻结报告 ID、正文、上下文、语言和 consents。截图期间关闭面板会取消尚未完成的动作。完成保存后,关闭面板或退出游戏不会为原反馈生成新身份。

SDK 收到报告回执 feedback_id 后,先保存它,再上传附件。每个附件保存独立幂等键、内容摘要、尝试次数和已确认的回执。重启会跳过已确认阶段,只用原内容、原幂等键重发未确认请求。响应丢失可能增加一次 HTTP 请求,但服务端幂等约束仍只保留一份报告或附件。游戏进程之外没有投递服务。

feedback_payload_ready 只表示准备好的意图。投递事实使用不含载荷的 feedback_delivery_changed(result) 通知:

状态 含义
sending 正在调度符合条件的请求,还没有确认回执。
queued 已保存在本地,等待具备发送条件或重试。
partial 报告已接受,部分附件尚未确认;retryable 表明是否继续自动恢复。不要为补传附件重新创建报告。
succeeded 报告和全部所选附件都有确认回执。
failed 未能完整确认结果,投递已停止;不代表服务端一定什么都没收到。

结果字段为 client_report_idfeedback_idstateretryablereasonattachments_totalattachments_acceptedattachments_failed。不包含反馈正文、联系方式、日志、Key、本地路径或签名 URL。不要打印完整 payload 或队列 JSON。团队工作流仍为 new/reviewing/resolved/ignored/archived;Console 只展示服务端已确认的报告与附件。

网络错误、408、429 和 5xx 可以在持久化预算内重试,遵守秒数及 HTTP 日期形式的 Retry-After。其他 4xx、本地文件缺失/为空/发生变化和耗尽次数会自动停止,同时保留已有部分回执。进度写盘失败后停止调度,直到 SDK 重新配置或启动,不覆盖最后成功保存的记录。配置请求最多重试 10 次;解决持续配置故障后可调用 refresh_sdk_config()。调度忙碌时刷新可能返回 false

项目归属

记录绑定规范化 endpoint、已知时由服务端验证的 project ID,以及不可逆 Project Key 指纹,不保存 Key 明文。已知项目更换 Key 后,必须先由配置接口确认相同项目、相同 endpoint,才能继续恢复;不同项目或 endpoint 不会静默接收旧载荷。

首次配置请求离线时,由本地队列选项决定是否保存最小记录;可选数据控件在项目策略确认前不可用。此时不会猜测 project ID,只允许原 Key 指纹与原 endpoint 在配置成功后补足归属。更换一个尚未验证的 Key,不能迁移这份记录,即使新 Key 后来属于同一项目。HTTP 不携带凭证跟随重定向。

本地 7 天期限与文件

本地未发送或失败的报告、死信及受管理附件,从首次落盘起最长保留 7 天(604800 秒)。已经保存过的附件可能使期限更早。重试、重新入队、更换 Key 和转入死信都不续期;请求前会拒绝到期条目。SDK 启动和活跃清理时删除到期数据,包括游戏树暂停或时间倍率为零时。游戏关闭后没有代码执行,物理清理要等下次启动,不存在宿主外后台清理服务。

无效时间和时钟回拨采用保守拒绝,不重新授予保留期。存储拒绝删除时返回 cleanup_failed,过期内容仍禁止补发,并在活跃期间重试本地清理,不会声称删除成功。这个期限不删除或改变服务端已经收到的数据;服务端保留期是独立政策。

SDK 拥有 user://echo_nebula_feedback/queue/dead_letter/attachments/,不要用这些目录保存宿主游戏文件。只管理经过校验的直接文件引用,拒绝路径越界和符号链接。启动时清理受管理孤儿文件,活跃期间也会清理遗留临时文件。当前 v3 记录继续使用冻结意图、分阶段回执、项目绑定和7天上限。旧 v1/v2 记录只从经过校验的 SDK 专有路径中删除,不迁移、不发送。删除被拒绝时保持不可发送并报告 cleanup_failed,直到本地清理成功。不可读内容最多保留有界、不含原始载荷的诊断。

验证

在 SDK 仓库执行 ./scripts/check-sdk-alpha.shGODOT_BIN=/path/to/godot ./scripts/run-godot-headless.sh。Python 工具只构造当前 payload fixture,队列和崩溃恢复由真实 Godot 门禁验证。

Infra 中的 node scripts/e2e-worktree-isolated.mjs --g2-sdk 会建立自有临时 API/PostgreSQL/MinIO/Console 栈,以真实 Addon 验证响应丢失、强制结束进程、附件恢复、项目隔离、到期和磁盘错误。追加 --g2-native 运行需要鼠标交互的原生窗口场景。提交后的 canonical 验收仍用 ./scripts/e2e-fresh-stack.sh,clean gate 不变。当前运行证据只覆盖实测桌面引擎,不构成全平台或导出游戏的支持承诺。

常见错误

下一步

阅读隐私与同意排障