chat-system¶
文件角色:implementation flow 投影;圖與步驟不得另建產品規則或參數 authority。
| Implementation authority | 產品 canon/流程 | 全域索引 |
|---|---|---|
| chat-system.md | 賽內機制 · 配對 | 流程.md §2 |
等待房共用文字聊天的技術流程圖(訊息傳送/結構/過濾/速率/最近隊友/生命週期)。模組實作見 chat-system.md;賽內改用 race-messages.md。
訊息傳送¶
sequenceDiagram
participant A as 玩家 A
participant Host as 房主 HostRoom
participant B as 玩家 B
participant FilterB as 過濾器 (B 端)
A->>A: 輸入訊息
A->>A: 速率限制檢查
A->>A: 長度驗證
A->>A: signPayload → SignedPayload<ChatMessage>
A->>Host: room-control 傳送原始 signed envelope
Host->>Host: admitted source + 驗章 + per-sender + room-wide 預算
Host->>B: 中繼同一份 signed envelope
B->>B: 驗章(簽章 / 時序窗 / nonce)+ sender=signer + roomId
B->>B: 驗 roomId、signer 與內容 shape
B->>B: 個人封鎖清單靜音檢查
B->>FilterB: 顯示前過濾
alt B 啟用過濾
FilterB->>B: 過濾後顯示
else B 關閉過濾
FilterB->>B: 原文顯示
end
訊息結構¶
graph TB
Env[SignedPayload〈ChatMessage〉] --> Msg[payload: ChatMessage]
Env --> Nonce[timestamp + nonce + signer + signature]
Msg --> Type[type: text or system]
Msg --> Sender[sender peerId + nickname]
Msg --> Room[roomId 情境綁定]
Msg --> Content[檢查 content 長度]
Msg --> TS[timestamp]
Msg --> ID[messageId]
Type -->|text| Text[玩家文字訊息]
Type -->|system| System[系統事件]
System --> S1[peer-joined]
System --> S2[peer-left]
System --> S3[host-changed]
System --> S4[match-started]
System --> S5[match-ended]
過濾流程¶
flowchart TD
Recv[收到訊息] --> Check{過濾開啟?}
Check -->|否| RawDisplay[顯示原文]
Check -->|是| Local[讀裝置本機 literal 詞表]
Local --> Norm[NFKC + case-fold + Unicode letter/number]
Norm --> Map[建立 normalized 到原文字位映射]
Map --> Match[substring 命中]
Match --> Replace[依映射替換原文範圍為 ***]
Replace --> FilteredDisplay[顯示過濾後]
Starter[Git starter list] --> Import{玩家明確匯入?}
Import -->|是| Copy[複製成玩家本機副本]
Import -->|否| Local
Copy --> Local
classDef consensus fill:#fdf3df,stroke:#c08a2d,stroke-width:1.4px,color:#3d2c0d;
classDef ui fill:#efeafa,stroke:#7a5cb8,stroke-width:1.4px,color:#2b1d4d;
class RawDisplay consensus
class FilteredDisplay ui
騷擾檢舉證據¶
flowchart LR
Select[玩家選同一 sender 的訊息] --> Cap{筆數符合證據上限?}
Cap -->|否| Keep[依上限截取]
Cap -->|是| Export[依 messageId 取原始 SignedPayload]
Keep --> Export
Export --> Encode[每筆 canonical DAG-CBOR + multibase base64url]
Encode --> Report[ReportEvent details + evidence string array]
Report --> Fold[ReportInfo 保留原值]
Fold --> Inbox[仲裁收件匣逐筆離線驗章]
Inbox --> Valid[顯示 valid / invalid、signer、messageId]
單一騷擾檢舉至多收錄同一 sender 的 16 筆訊息;完整逐筆大小與編碼限制見 流程/檢舉與仲裁.md §1。
速率限制(雙端)¶
flowchart TD
Send[使用者按送] --> Check[計算發送窗口內筆數]
Check --> Count{尚有發送額度?}
Count -->|是| Allow[允許送出]
Count -->|否| Block[阻擋 + 警示「訊息過快」]
Allow --> Record[記錄時間戳]
Recv[收到訊息] --> Verify{驗章通過?}
Verify -->|否| Drop[丟棄]
Verify -->|是| RxCount{該 sender 接收速率合規?}
RxCount -->|是| Accept[接受 → 過濾顯示]
RxCount -->|否| Drop
classDef local fill:#e8f3ec,stroke:#3f8f5f,stroke-width:1.4px,color:#173525;
classDef fail fill:#fae9e7,stroke:#b4544a,stroke-width:1.4px,color:#471d18;
class Allow,Accept local
class Block,Drop fail
送端自律是 UX、收端強制是防線(惡意 client 可跳過送端檢查)。
最近隊友更新¶
sequenceDiagram
participant Race as participant 已驗證正式結果
participant Service as RecentTeammates Service
participant DB as IndexedDB
Race->>Service: addFromMatch(match-start 固定 roster)
loop 每位 participant
Service->>DB: get peer data
alt 已存在
Service->>DB: update lastPlayedAt + matchCount++
else 新增
Service->>DB: insert 初始 matchCount
end
end
Service->>DB: 取所有 entries
Service->>Service: 排序 by lastPlayedAt
alt 超過歷史保留門檻
Service->>DB: 刪除最舊的
end
開賽失敗、賽前取消、單純 teardown 與 spectator 不進入此流程。設定頁隱私區讀取這份本機 清單,供玩家辨識最近隊友並直接加入個人封鎖清單;資料不建立好友關係且不上鏈。
房內聊天 UI¶
graph TB
Room[房間] --> ChatWindow[聊天視窗]
ChatWindow --> History[訊息歷史<br/>上限取自 CHAT_HISTORY_LIMIT]
ChatWindow --> Input[輸入框]
ChatWindow --> Toggle[☐ 文字過濾]
Input --> RateCheck{速率檢查}
RateCheck -->|通過| Send[送出]
RateCheck -->|拒絕| Toast[「訊息過快」]
History --> Each[每則訊息]
Each --> Sender[發送者顯示身分]
Each --> Time[時間戳]
Each --> Text[訊息內容過濾後]
Toggle -.->|on| Filtered
Toggle -.->|off| Raw
發送者顯示為暱稱加空格與 # 後 8 位 hex 指紋;缺暱稱時只顯示該穩定指紋。
訊息生命週期¶
stateDiagram-v2
[*] --> Composed: 玩家輸入
Composed --> Sent: room-control 送給 host
Sent --> Received: host 驗證後中繼
Received --> Filtered: 過濾後顯示
Received --> Raw: 不過濾顯示
Filtered --> InBuffer: 加入歷史
Raw --> InBuffer
InBuffer --> RoomEnd: 房間結束
RoomEnd --> [*]: 訊息消失(不上鏈)