Files
CloudSSH/tests/api-errors.test.ts
T
TanXinandTanXin 2dc1f5f290 release: 发布 v1.11.0 会话秒级恢复与分享设备绑定版本 (#102)
## 发布 v1.11.0 会话秒级恢复与分享设备绑定版本

### 版本要点

- **Durable Object 会话保持**:异常断线进入 60 秒保持期,有界缓冲 + SSH 背压暂停,恢复后一次性补发并续借
Window 额度
- **分享会话设备绑定秒级恢复**:认领时绑定非可导出 ECDSA P-256 公钥,断线重连需挑战签名验签(nonce
单次防重放);token 每次轮换并容忍上一代(覆盖轮换帧丢失)
- **严格恢复口径**:分享会话仅绑定设备后可恢复,未绑定环境断线即提示并终结,绝不走完整重连兜底(与二次消费隔离)
- **体验完善**:指数退避铺满 58s 保持窗口、到期前 1 分钟预警、恢复后延迟基线重发、全链路中英文本地化、恢复失败友善提示
- **质量**:新增 9 例分享恢复安全测试矩阵;撤销盲区、SFTP 断链、GitHub 鉴权门等缺陷修复

### 提交列表(17 个,fa24813..5aff4bf 含 1bdd80d 后续)

```
1bdd80d feat: 实现 Durable Object 会话保持与单设备断线秒级无缝重连
f17f15b feat: 分享会话秒级恢复与设备绑定验证
8be962c fix: 修复分享恢复测试中时间戳二次取值导致的验签偶发失败
3e2ad36 fix: 分享会话恢复改为指数退避铺满宽限窗口
749d281 refactor: 分享落地页改用 DOM API 构建替代 innerHTML 拼接
1fe0c2e fix: 补全分享链路的中英文本地化缺口
97a7797 fix: 修复分享错误映射层泄漏词条键名导致的 E2E 失败
3c6c946 refactor: 清理分享管理器存量告警并固化内联抑制策略说明
b23ca94 fix: 恢复会话后补发双段延迟基线并增强分享恢复失败诊断
0d4039d fix: 分享恢复失败原因的友善提示与设备绑定可靠性校验
0d5ffb7 fix: 分享恢复末次尝试的窗口边界处理与明确提示
d0a0b01 fix: 设备绑定状态随会话下发并更正提示语义
d702d80 fix: 分享会话恢复统一要求设备绑定并即时提示不支持场景
9511fa8 fix: 分享会话到期预警、终端终态识别与严格恢复口径收尾
b78e317 fix: 分享恢复容忍上一代 token,修复弱网轮换帧丢失导致的永久锁死
0db34bb fix: 更正无痕环境认领提示以对齐严格恢复口径
fa24813 refactor: 移除认领时设备绑定失败的落地页提示(方案 B)
5aff4bf release: 发布 v1.11.0 会话秒级恢复与分享设备绑定版本
```

### 验证结果

- typecheck 双端通过(worker + frontend)
- 全量测试 636/636(53 文件,含新增分享恢复安全矩阵)
- CI 门禁(deploy.yml):typecheck → test → build:frontend → test:e2e →
按分支部署,已在 test 分支历次推送中通过

### 说明

- CHANGELOG 与版本号(package.json / frontend/package.json)已更新至
1.11.0(2026-08-25)
- 合入 main 后将自动部署生产环境 `cloudssh`

---------

Co-authored-by: TanXin <tanxin@example.com>
2026-08-25 11:38:12 +08:00

28 lines
1.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { localizedApiError } from '../frontend/src/api-errors';
import { t } from '../frontend/src/i18n';
describe('localizedApiError', () => {
it('已知服务端消息翻译为当前语言文案而非词条键名', () => {
const result = localizedApiError(
{ error: 'This share link has already been used' },
'share.claimFailed'
);
// 回归防护:曾出现直接返回键名 "share.errorAlreadyUsed" 泄漏到界面的缺陷
expect(result).toBe(t('share.errorAlreadyUsed'));
expect(result).not.toMatch(/^share\./);
});
it('未知消息透传原文便于排查', () => {
expect(
localizedApiError({ error: 'Some future server message' }, 'share.claimFailed')
).toBe('Some future server message');
});
it('缺少 error 字段或负载非法时回退兜底词条', () => {
expect(localizedApiError({}, 'share.claimFailed')).toBe(t('share.claimFailed'));
expect(localizedApiError(null, 'share.claimFailed')).toBe(t('share.claimFailed'));
expect(localizedApiError(undefined, 'share.loadFailed')).toBe(t('share.loadFailed'));
});
});