// packages/contracts/src/realtime.ts // WS 实时消息契约(P2,技术实施方案 §7):OpenAPI 不覆盖 WS 帧,本文件单独 // 维护,与 openapi.yaml 生成类型同属契约单一事实来源。消息一律为以 type 判别 // 的联合;新增/修改消息类型须同步 __tests__/realtime.test.ts 的穷尽断言。 /** 心跳间隔(毫秒):客户端周期发送 ping、服务端回 pong;超时无心跳的连接由服务端踢除。 */ export const HEARTBEAT_MS = 20000; /** * ov 入口 WS 网关挂载路径:与 HTTP 同端口(ov 8081),升级请求按本路径路由 * (services/api realtime 网关装饰器与 e2e/viewer 客户端共用,单一来源)。 */ export const REALTIME_WS_PATH = '/v1/realtime'; // ── server → client ── /** 照片发布:副本应用后向相册房间广播(ov 侧,下同)。 */ export interface PhotoPublishedMessage { type: 'photo.published'; photoId: string; albumId: string; publishedAt: string; // ISO UTC entityVersion: number; } /** 照片隐藏。 */ export interface PhotoHiddenMessage { type: 'photo.hidden'; photoId: string; albumId: string; entityVersion: number; } /** 照片删除(软删除的公开语义等同隐藏,客户端行为一致:即刻移出列表)。 */ export interface PhotoDeletedMessage { type: 'photo.deleted'; photoId: string; albumId: string; entityVersion: number; } /** 点赞合并广播:短窗内同照片批次合并为一次 burst;originBatchIds 供客户端过滤自身批次。 */ export interface LikesBurstMessage { type: 'likes.burst'; photoId: string; albumId: string; delta: number; total: number; // 服务端权威累计 countVersion: number; burstId: string; originBatchIds: string[]; // 合并进本 burst 的批次 ID(含己则抑制自身动画) } /** 相册浏览数更新。 */ export interface StatsUpdatedMessage { type: 'stats.updated'; albumId: string; viewCount: number; countVersion: number; } /** 心跳应答:回显客户端 ping 携带的 ts。 */ export interface PongMessage { type: 'pong'; ts: number; } /** * 错误帧:客户端消息无法处理时的服务端应答(如 subscribe 的相册不存在/ * 不公开)。连接保持打开——客户端可修正后重发(如改订其它相册或等待恢复)。 */ export interface RealtimeErrorMessage { type: 'error'; /** 契约 ErrorCodes 域码(subscribe 校验失败 → ALBUM_NOT_PUBLIC 等)。 */ code: string; /** 可读说明(可选,不承诺稳定性,客户端只应消费 code)。 */ message?: string; } export type ServerMessage = | PhotoPublishedMessage | PhotoHiddenMessage | PhotoDeletedMessage | LikesBurstMessage | StatsUpdatedMessage | PongMessage | RealtimeErrorMessage; // ── client → server ── /** 订阅相册房间;服务端校验相册公开后加入。 */ export interface SubscribeMessage { type: 'subscribe'; albumToken: string; sinceVersion?: number; // 缺省从订阅时刻起推送,不补播历史 } /** 退订相册房间。 */ export interface UnsubscribeMessage { type: 'unsubscribe'; albumToken: string; } /** 心跳;服务端原样回 pong。 */ export interface PingMessage { type: 'ping'; ts: number; // epoch 毫秒 } export type ClientMessage = SubscribeMessage | UnsubscribeMessage | PingMessage;