Middleware 與 Events

使用通用 Middleware 和 Events 擴充 Transport、Endpoint、Service 與協同擴充模組。

Middleware 和 Events 是 Collaboration SDK 的通用擴充點。身分與授權透過 Middleware 接入。操作執行前使用 Middleware 驗證及限制,狀態確認後使用 Events 記錄變更或觸發後續工作。兩者均可支援日誌、請求追蹤、指標與衍生狀態更新。

兩種擴充時機

MiddlewareEvent
執行時機操作期間狀態變更確認後
能否參與或拒絕操作能不能
能否補充呼叫 Context能讀取 Event 攜帶的 Context 與結果
監聽器失敗可能使目前操作失敗與已確定的領域結果隔離
典型用途身分、授權、驗證、日誌、計時衍生索引、快取、指標、程序內通知

需要決定操作能否繼續時使用 Middleware;需要觀察已發生的狀態變更時使用 Event。

Middleware 執行模型

相同 Action 的 Middleware 按註冊順序形成非同步鏈。await next() 進入下一個 Middleware;SDK 操作在最內層執行:

TypeScript
service.use("submitChangeset", async (context, next) => {  const startedAt = performance.now();  context.customData.startedAt = startedAt;  try {    await next();  } finally {    metrics.observe("collaboration.submit.duration", performance.now() - startedAt);  }});

Middleware 可以:

  • 讀取特定 Action 的請求;
  • 透過 customData 共享本次呼叫資料;
  • 在 next() 前檢查輸入或查詢應用系統;
  • 在 next() 傳回後記錄耗時或結果;
  • 拋出 CollabError 拒絕預期中的協同操作。

Transport HTTP Middleware 可不呼叫 next() 而直接結束 HTTP 回應。Service 和 Endpoint Middleware 應以明確錯誤拒絕操作,不應默默略過 next()。

Transport Middleware

transport.use() 註冊一般 HTTP Middleware,依註冊順序執行。呼叫 await next() 繼續處理,或直接結束回應。transport.useUpgrade() 則處理 WebSocket 握手。

例如,先記錄 HTTP 耗時,再驗證身分並設定可信的 userID,最後註冊 Collaboration Endpoint:

TypeScript
transport.use(async (context, next) => {  const start = performance.now();  context.response.once("finish", () => {    recordHttpDuration({      method: context.incomingMessage.method,      route: context.route?.path ?? "unmatched",      status: context.response.statusCode,      durationMs: performance.now() - start,    });  });  await next();});transport.use(async (context, next) => {  const user = await authenticate(context.incomingMessage);  if (!user) {    context.response.statusCode = 401;    context.response.end("Authentication required");    return;  }  context.userID = user.userId;  await next();});transport.register(collaborationEndpoint);

recordHttpDuration 和 authenticate 由應用程式提供。Endpoint 使用前序 Middleware 設定的 userID。觀測 Middleware 註冊在驗證之前,因此也會記錄驗證拒絕。

context.route?.path 是匹配後的完整路由範本,在執行到 Endpoint 時才可用。透過 response 的 finish 事件量測回應完成的耗時;路由匹配前的拒絕與未知路徑記為 unmatched。

Endpoint Middleware

Endpoint Middleware 參與即時 Session 與 Presence 操作:

Action執行時機常見用途
connect票證消耗及 Session 建立後、HELLO 前連線策略、成員顯示名稱與頭像
joinUnitSession 加入 Unit Room 前房間准入與 Unit 可見性
receivePresence收到已加入 Session 的 Presence 後、廣播前驗證與 Presence 篩選
sendPresence向每個 Room 成員傳送 Presence 前按接收者篩選 Presence
TypeScript
endpoint.use("receivePresence", async (context, next) => {  if (JSON.stringify(context.payload).length > 8_000) {    throw new CollabError("INVALID_REQUEST", "Presence payload is too large");  }  await next();});

Endpoint 的 session.userID 和 session.customData 來自簽發 Session Ticket 的 HTTP 請求。Endpoint 為目前 WebSocket Session 建立 memberID,重新連線後會改變。

Service Middleware

Service Middleware 參與權威協同資料的生命週期:

Action邊界常見用途
readUnitData讀取快照、區塊或變更集前讀取策略、租戶檢查、稽核
createUnit原子建立 Unit 前類型檢查、建立策略、業務規則
deleteUnits寫入批次刪除前刪除策略與逐 Unit 檢查
recoverUnits寫入批次還原前還原策略與稽核
submitChangeset邏輯提交進入冪等與 OT 前編輯策略、總大小限制、追蹤
applyChangeset將轉換後變更集套用至候選狀態前mutation 層級驗證
commitChangesetAdapter 修訂版本 CAS 與提交前最終修訂版本規則與指標
TypeScript
service.use("createUnit", async (context, next) => {  await creationPolicy.assertAllowed({    userID: context.userID,    unitID: context.request.snapshot.unitID,    type: context.request.snapshot.type,  });  context.customData.creationPolicyChecked = true;  await next();});

每次邏輯提交執行一次 submitChangeset。修訂版本競爭可能讓 applyChangeset 和 commitChangeset 以新的 attempt 再次執行。這些階段的 Middleware 必須可安全重試,不能直接傳送不可逆的外部訊息或執行一次性扣款。

customData 範圍

customData 是應用可寫入、僅限本次呼叫的物件,適合儲存:

  • 租戶 ID、追蹤 ID 或請求記錄器;
  • 本次呼叫重用的 ACL 查詢結果;
  • 計時起點或稽核標籤;
  • 同一 Middleware 鏈共享的業務物件。

一般 HTTP 請求、Service 呼叫與 WebSocket Session 的生命週期不同。customData 不會自動持久化、傳送至瀏覽器或寫入日誌。長期資料仍應存入應用儲存。

Event 執行模型

Service 以 on() 註冊程序內監聽器,並取得可釋放訂閱:

TypeScript
const subscription = service.on("changesetCommitted", async (event) => {  metrics.increment("collaboration.changeset.committed", {    unitType: String(event.changeset.type),  });  localCache.delete(event.changeset.unitID);});subscription.dispose();

相同 Event 的監聽器按註冊順序依序執行並被等待。監聽器錯誤會記錄並與已確定的領域結果隔離。例如,changesetCommitted 監聽器失敗,不會回滾 Adapter 已儲存的變更集。

因此 Events 適合程序內、可還原或非關鍵的後續工作。它們不與資料庫提交共享交易邊界,也不保證送達外部系統。

核心 Service Events

Event已確認事實典型用途
unitCreatedUnit 與初始資料已建立本機目錄快取或衍生索引
changesetCommitted變更集已確認並推進修訂版本廣播、指標、衍生索引
unitsDeletedAdapter 已確認批次刪除清除程序內相關狀態
unitsRecoveredAdapter 已確認批次還原還原程序內衍生狀態

Endpoint 監聽同一 Service 的 changesetCommitted,將已確認變更集廣播給目前程序中已加入 Unit 的用戶端;設定了 pub/sub 介面卡時,也會投遞到其他節點。Endpoint 也對主動離開、斷線與 Endpoint 釋放提供 memberLeftUnit。

擴充模組具有獨立擴充點

History、Thread Comment 和 Worktree 不繼承核心 Collaboration Service 的 Middleware 或 Events:

  • History 提供 getHistoryList、listHistoryCreators、getHistoryChangesets 讀取 Middleware;
  • Comment 提供新增、回覆、編輯、刪除、解決、清單 Middleware 與 commentCommitted;
  • Worktree 提供生命週期與草稿 read/submit/commit Middleware,以及建立、狀態、合併與草稿提交 Events。

為每個啟用的模組分別安裝規則。核心 Service 上的日誌、租戶或授權 Middleware 不會自動涵蓋它們。

繼續閱讀身分與授權,瞭解如何將既有使用者與 ACL 對應到這些通用 Middleware 邊界。

你覺得這篇文檔如何?

© 2026 DreamNum Co., Ltd.