Browse Realtime

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。

Shell example
: "${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。

Shell example
: "${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 原生 WebSocketEventSource 無法設定 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

TypeScript example
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,每次都呼叫 freshGrantfreshGrantrefreshState 都收到 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。

Shell example
: "${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