搜尋完整文件

輸入關鍵字,例如 idempotency、proposal lifecycle 或權限。

選擇 開啟
English
瀏覽文件

Relay · 即時事件

即時事件與重新連線復原

WebSocket 負責傳遞即時對話事件。事件延遲、重新連線後重複送達,或與 REST 補齊結果混合時,channel-local seq 仍是排序依據。

建立連線

連到 Phoenix socket endpoint,並傳入 REST API 使用的同一個 host-signed JWT:

import { Socket } from "phoenix";

const socket = new Socket("wss://relay.example.com/socket", {
  params: { token: relayToken }
});

socket.connect();

Server 先驗證 token,client 才能加入任何 channel。

加入 channel

Topic 包含已簽章的 tenant ID 與 channel ID:

tonetify-relay:<tenant_id>:<channel_id>

傳入 client 已經套用的最後一個 sequence:

const channel = socket.channel(
  `tonetify-relay:${tenantId}:${channelId}`,
  { last_seq: lastAppliedSeq }
);

channel.join();

Relay 在 join 時檢查兩個條件:

  • Topic tenant 必須等於 token 的 tenant_id
  • Token 的 user_id 必須是 channel member。

Membership 或 tenant 檢查失敗時回傳 not_a_member。Join 超過設定的 frame budget 時回傳 rate_limited

Join 後的事件

Join 成功後,Relay 會傳送目前的 presence state、要求補齊的 message,以及 channel unread count。

事件意義
presence_stateChannel 目前的 presence snapshot
presence_diffSnapshot 之後的 presence change
msg:new即時 message,或 catch-up window 內的一則 message
catch_up:truncated這個 catch-up window 之後仍有 message
unread_count這個 user 與 channel 目前的 unread count

這五種事件構成契約測試版的穩定即時核心。Edit、retract、pin、reaction、thread 與 typing event 目前屬於預覽介面。它們已存在於實作與 SDK,但事件名稱、payload 與恢復行為尚未納入公開相容性政策。

Catch-up window

Join payload 包含 last_seq 時,Relay 會讀取 sequence 大於該值的 message,再依升冪逐筆 push msg:new。WebSocket catch-up window 預設最多 200 則 message。

如果仍有後續 message,Relay 會在這批資料之後傳送以下事件:

{
  "channel_id": "channel-uuid",
  "after_seq": 42,
  "next_after_seq": 242,
  "limit": 200
}

catch_up:truncated 表示這批資料結束於 next_after_seq,不代表 Relay 遺失 message。

使用新的 last_seq 再次 join,或透過 REST 從該 sequence 繼續:

GET /api/channels/{channel_id}/messages?after_seq=242&limit=100
Authorization: Bearer <jwt>

REST 每次最多回傳 100 則 message。持續請求,直到 response 少於 100 則。

重新連線演算法

可靠的 client 應遵循以下順序:

  1. 依 channel 保存已完整套用的最大 seq
  2. 開始復原前,先訂閱即時 msg:new event。
  3. 使用保存的 sequence join。
  4. 依 message id 合併 catch-up 與即時 message。
  5. seq 顯示 message,不依 arrival order。
  6. Catch-up 被截斷時,從 next_after_seq 繼續,直到補完缺口。
  7. Message 進入 local store 後,才更新保存的 sequence。

Catch-up message 可能與 local store 內的 message 重複。復原期間允許 duplicate delivery;穩定的 message idseq 讓 merge result 保持一致。

斷線期間傳送

Send timeout 代表 write result 不確定。連線恢復後,保留原 nonce 並重試。如果第一次 attempt 已經 commit,Relay 會回傳既有 message。

不要只因 transport disconnected 就產生新 nonce。新 nonce 代表新的 logical message。

目前的 SDK 邊界

目前的 @tonetify/relay 提供:

  • 使用 exponential backoff 與 jitter 重新連線 socket
  • 預設 30 秒 heartbeat
  • 透過 channel.join(lastSeq) 執行 catch-up join
  • 使用 MessageStore 排序並抑制 duplicate
  • 使用 CatchUpManager 在 join 前讀取 store 最新的 sequence
  • catch_up:truncated event

有兩個復原步驟尚未自動完成:

  • In-memory MessageStore 不會跨 page reload 或 process restart 保存最後的 sequence。
  • Socket 自動 rejoin 不會把 join parameter 更新為最新套用的 sequence,CatchUpManager 也不會自動排空 truncated window。

在這些缺口補齊前,host 應保存 last_seq,並在 socket 重新連線或 application 再次進入 active state 時執行 REST catch-up。先依 id 合併 REST 與 WebSocket message,再以 seq 排序。

這是 SDK productization gap。Server reconnect contract 已支援完整復原。

下一步:了解 channel lifecycle 與 permission boundary