stable · guide · 0.1.0
連線、publish 與 subscribe
從 Project Backend 核發 grant
Project Credential 必須保留在 Project Backend,且需要 realtime:connect scope。呼叫
POST /realtime-grants,只放入目前 subject 真正可用的 channels 與 capabilities。不要讓 browser 自行選擇任意 channel。
: "${MINICENTER_PROJECT_CREDENTIAL:?set the sandbox Project Credential}"
: "${MINICENTER_URL:=https://sandbox.minicenter.otus.tw}"
curl --fail-with-body \
--request POST \
--header "Authorization: Bearer ${MINICENTER_PROJECT_CREDENTIAL}" \
--header "Content-Type: application/json" \
--data '{"subject":"YOUR_PROJECT_SUBJECT","channels":["YOUR_PROJECT_DEFINED_CHANNEL"],"capabilities":["subscribe"]}' \
"${MINICENTER_URL}/api/v1/realtime-grants"
SSE subscribe
以下使用支援 Authorization header 的 curl 保持 SSE stream。每個 data: 值是未填補 = 的 base64url;decode 後才是原始 message bytes。
: "${MINICENTER_REALTIME_GRANT:?request a short-lived grant}"
: "${MINICENTER_REALTIME_URL:=https://realtime.minicenter.otus.tw}"
curl --no-buffer --fail-with-body \
--header "Authorization: Bearer ${MINICENTER_REALTIME_GRANT}" \
"${MINICENTER_REALTIME_URL}/sse?channel=YOUR_PROJECT_DEFINED_CHANNEL"
WebSocket 使用 GET /ws?channel=...、相同的 Authorization: Bearer header,收到的 frame 是 binary raw bytes。Node/server clients 可繼續使用既有 WebSocket、SSE 與 HTTPS contracts。Browser 原生 WebSocket 與 EventSource 無法設定 Authorization header;browser 應使用官方 BrowserRealtimeClient 的 fetch-streamed SSE transport,不能把 grant 放進 URL query。
Official browser SDK
Use a same-origin Project Backend BFF. It first verifies its session and channel membership, then calls MiniCenter with the Project Credential; the browser freshGrant callback receives only a minimal five-minute fresh grant. Project Credential never enters the browser, and the SDK puts the grant only in the Authorization: Bearer header, never cookies, storage, logs, analytics, or telemetry.
SDK 為每個 subscribe 或 publish flow 建立穩定的 X-Correlation-ID 與可選 traceparent,並把相同 context 和 requested channel 交給 Project Backend callbacks。BFF 必須用 (verified session, requested channel, capability) 做 Membership authorization,且只為該 channel 核發最小 grant;不能把 channel 丟掉或改成 browser 自選的 broader set。gateway CORS 允許的完整 header set 是 Authorization, Content-Type, X-Correlation-ID, and optional traceparent。
import { BrowserRealtimeClient } from "@minicenter/sdk";
export function connectProjectRealtime({ baseUrl, projectClientId, grantEndpoint, stateEndpoint, channel, onMessage, onError, renderState }) {
const realtime = new BrowserRealtimeClient({
baseUrl,
projectClientId,
freshGrant: async ({ channel: requestedChannel, capability, correlationId, traceparent }) => {
const response = await fetch(grantEndpoint, {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json", "X-Correlation-ID": correlationId, ...(traceparent ? { traceparent } : {}) },
body: JSON.stringify({ channel: requestedChannel, capability }),
});
if (!response.ok) throw new Error("Project Backend rejected Realtime access");
return (await response.json()).grant;
},
refreshState: async ({ correlationId, traceparent }) => {
const response = await fetch(stateEndpoint, {
credentials: "same-origin",
headers: { "X-Correlation-ID": correlationId, ...(traceparent ? { traceparent } : {}) },
});
renderState(await response.json());
},
});
const subscription = realtime.subscribe({ channel, onMessage, onError });
window.addEventListener("pagehide", () => subscription.close(), { once: true });
return { realtime, subscription };
}
grantEndpoint, stateEndpoint, channel, and rendering callbacks are Project Backend-owned inputs; MiniCenter requires no route name, channel namespace, or UI behavior. The published examples/browser-realtime-bff.js shows the matching project-neutral BFF boundary.
SDK 以 bounded exponential backoff 與 jitter reconnect,每次都呼叫 freshGrant。freshGrant 與 refreshState 都收到 AbortSignal,close 不會被 stalled callback 卡住。Reconnect 會在重新 subscribe 前先等待 refreshState 讀取 Project Backend authoritative state;SDK 不建立 history、replay、unread state、membership、moderation 或任何 project business semantics。完整可執行 browser 與 BFF examples 位於 TypeScript SDK 的 examples/。
Publish
具有 publish capability 的 grant 可 POST /publish?channel=...,body 是 1–32768 raw bytes;成功回應 202 Accepted。訊息格式與版本由 Project Backend 擁有,gateway 不保存或解析內容。
Browser 可呼叫 await realtime.publish(channel, bytes);SDK 會透過同一 freshGrant callback 取得 publish grant 並只放在 Authorization header。
: "${MINICENTER_REALTIME_GRANT:?request a grant with publish capability}"
: "${MINICENTER_REALTIME_URL:=https://realtime.minicenter.otus.tw}"
curl --fail-with-body \
--request POST \
--header "Authorization: Bearer ${MINICENTER_REALTIME_GRANT}" \
--header "Content-Type: application/octet-stream" \
--data-binary '{"type":"refresh"}' \
"${MINICENTER_REALTIME_URL}/publish?channel=YOUR_PROJECT_DEFINED_CHANNEL"
Message wire contract
- HTTPS publish body:1–32768 arbitrary raw bytes。
- WebSocket subscribe:每則訊息是內容完全相同的 binary frame。
- SSE subscribe:
data:是同一組 bytes 的 unpadded base64url encoding;decode 後的大小仍須為 1–32768 bytes。
Realtime Message 不進入 Event schema pipeline,因為它不保證 durable delivery、history 或 replay。需要 canonical Event payload 與 at-least-once delivery 時改用 Events & Webhooks。