跳到內容

使用 TypeBox 作為通訊協定的單一事實來源

處理跨平台的通訊協定時,最痛苦的就是要手動同步不同語言的型別定義。一旦後端改了欄位,前端或行動端沒跟上,程式碼就會在執行時悄悄崩潰,這種手動維護多套 Schema 的方式既低效又容易出錯。

我們選擇使用 TypeBox 這個 TypeScript-first 的 Schema 函式庫來定義 Gateway WebSocket 協定(包含握手、請求/回應、伺服器事件)。透過這個單一事實來源,我們可以自動驅動執行時驗證、匯出 JSON Schema,甚至為 macOS App 生成 Swift 程式碼。

在開始之前,請確保你的環境符合以下條件(依據源文件):

  • pnpm 套件管理器
  • Node.js 開發環境
  • 專案原始碼路徑:src/gateway/protocol/schema.ts 與 src/gateway/server.ts

Gateway 的通訊邏輯非常直覺,所有的 WebSocket 訊息都屬於以下三種框架之一:

  • Request: { type: "req", id, method, params }
  • Response: { type: "res", id, ok, payload | error }
  • Event: { type: "event", event, payload, seq?, stateVersion? }

連線後發送的第一個訊息必須是 connect 請求。之後你才能呼叫其他方法(如 health, send)或訂閱事件。

用戶端 Gateway
|---- req:connect -------->|
|<---- res:hello-ok --------|
|<---- event:tick ----------|
|---- req:health ---------->|
|<---- res:health ----------|

這是一個最簡單的用戶端實作,示範如何完成連線並檢查健康狀態:

import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => {
ws.send(
JSON.stringify({
type: "req",
id: "c1",
method: "connect",
params: {
minProtocol: 3,
maxProtocol: 3,
client: {
id: "cli",
displayName: "example",
version: "dev",
platform: "node",
mode: "cli",
},
},
}),
);
});
ws.on("message", (data) => {
const msg = JSON.parse(String(data));
if (msg.type === "res" && msg.id === "c1" && msg.ok) {
ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" }));
}
if (msg.type === "res" && msg.id === "h1") {
console.log("health:", msg.payload);
ws.close();
}
});

假設你要新增一個 system.echo 方法,傳入文字並回傳 { ok: true, text }。

在 src/gateway/protocol/schema.ts 中加入定義,並導出型別:

export const SystemEchoParamsSchema = Type.Object(
{ text: NonEmptyString },
{ additionalProperties: false },
);
export const SystemEchoResultSchema = Type.Object(
{ ok: Type.Boolean(), text: NonEmptyString },
{ additionalProperties: false },
);
// 註冊到 ProtocolSchemas 並導出 Static 型別
export type SystemEchoParams = Static<typeof SystemEchoParamsSchema>;
export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;

在 src/gateway/protocol/index.ts 中,匯出 AJV 驗證函式:

export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);

在 src/gateway/server-methods/system.ts 加入處理器:

export const systemHandlers: GatewayRequestHandlers = {
"system.echo": ({ params, respond }) => {
const text = String(params.text ?? "");
respond(true, { ok: true, text });
},
};

別忘了在 src/gateway/server.ts 的 METHODS 列表中加入 "system.echo"。

執行以下指令來更新 JSON Schema 與 Swift 模型:

Terminal window
pnpm protocol:check

如果你在開發過程中遇到問題,請檢查以下源文檔提到的常見狀況:

  • 連線被拒絕:檢查 connect 請求是否為第一個發出的訊息。伺服器會驗證 minProtocol 與 maxProtocol,如果版本不匹配會直接拒絕。
  • 欄位驗證失敗:大部分物件都開啟了 additionalProperties: false,請確保發送的 JSON 沒有包含多餘的欄位。
  • Swift 端的未知訊息:Swift 生成器會保留未知框架類型(保留原始 payload),這是為了確保舊版用戶端不會因為收到新事件而直接當機。
  • 副作用失效:對於有副作用的方法(如 send, chat.send),請確保在 params 中帶上 idempotencyKey。

想要更自動化的開發體驗?試試 AI Setup Assistant。

OpenClaw

OpenClaw Expert

還是卡住了?

如果這篇文件沒有解決你的情境,直接問 OpenClaw Expert,拿到可執行步驟。