Skip to content

@moosehq/game-client-sdk 參考

完整講解見生命週期上報指南。

這個包有一個根匯入和三個子路徑匯出

  • @moosehq/game-client-sdk(根匯入) —— 你遊戲客戶端程式碼(執行在運營商 iframe 裡)使用的 GameBridge 類別。
  • @moosehq/game-client-sdk/protocol —— 雙方約定的訊息契約。GameBridge 內部會用到它;只有當你要在不透過 GameBridge 的情況下直接說這套協議時(例如寫一個扮演 shell 角色的測試工具),才需要直接匯入它。
  • @moosehq/game-client-sdk/behavior —— BehaviorCollector,機器人偵測遙測背後單純的互動時間彙整器。和 GameBridge/protocol 無關,完全不做任何 postMessage,也不做任何網路 I/O。
  • @moosehq/game-client-sdk/behavior-reporter —— BehaviorReporter,負責接網路的對應角色:持有一個 BehaviorCollector,並把它產生的摘要直接送給平台。實際接入時你會用的其實是它——見機器人偵測訊號

所有金額欄位(amountMinorbalanceMinorwinAmountMinor)都是最小貨幣單位(例如分)的整數——和錢包 API 的慣例一致。

請注意,玩家要求的語言不屬於這套協議——GameBridge 只攜帶下面這些遊戲 → shell 方向的生命週期事件。語言是反方向、平台 → 遊戲的資訊——見啟動網址的 lang 查詢參數,或 VerifySessionResponse.config.language

@moosehq/game-client-sdk

GameBridge

ts
new GameBridge(options: GameBridgeOptions)
選項型別說明
targetPostMessageTarget預設為 window.parent。主要在測試時才需要覆寫。

方法

  • notifyGameLoaded(balanceMinor: number): void —— 在你的遊戲載入完成後立即呼叫一次,帶上玩家目前的餘額(來自你自己的 session-verify/餘額查詢),讓 shell 能立刻顯示它,不必等到第一筆下注結算。
  • notifyBetStart(amountMinor: number, balanceMinor: number): void —— 在這一局的結果已經確定(例如你伺服端的下注/結算呼叫剛返回)、即將開始揭曉它的時候呼叫一次——不是在下注之前。balanceMinor 是這筆下注「扣款之後」、但「派彩套用之前」的餘額,讓 shell 能立刻看到扣款,而不會被提早揭露的派彩破壞你揭曉動畫的效果。如果下注被拒絕,完全跳過這個呼叫——因為根本沒有下注成功——直接呼叫 notifyBetEnd
  • notifyBetEnd(outcome: 'win' | 'loss' | 'declined' | 'rolled_back', amountMinor: number, balanceMinor: number, winAmountMinor?: number): void —— 在你的揭曉動畫結束後呼叫;balanceMinor 是最終、已完全結算的餘額。只有當 outcome === 'win' 時,winAmountMinor 才有意義(也才應該傳入)。
  • notifyBalanceExhausted(): void
  • notifyExitGame(): void —— 玩家點選了遊戲內的「返回大廳」控制元件,遊戲無法關閉自己所在的 iframe,所以請求 shell 來處理。
  • notifySessionRevoked(reason?: string): void —— 運營商在遊戲仍開啟時於伺服端結束了這個玩家的 session(POST /v1/operator/players/kick,詳見伺服端接入指南)。平台沒有直接推送到瀏覽器的通道——你是透過自己的方式得知這件事的(例如下一次錢包呼叫收到 401,或者你自建的伺服端推送通道;完整契約見Session 撤銷回呼,該契約通知的是你的伺服端)——並在這裡上報,讓 shell 可以顯示「您已被登出」並關閉 iframe,這和 notifyExitGame 代表的主動「返回大廳」不同。

@moosehq/game-client-sdk/protocol

shell 端沒有包裝類別——運營商直接針對這個模組實現自己的監聽器(參考監聽器範例見生命週期上報指南)。匯出內容:

ts
// Game -> Shell —— 這套協議目前唯一的方向
type GameEvent =
  | { type: 'GAME_LOADED'; balanceMinor: number }
  | { type: 'BET_START'; amountMinor: number; balanceMinor: number }
  | {
      type: 'BET_END'
      outcome: 'win' | 'loss' | 'declined' | 'rolled_back'
      amountMinor: number
      balanceMinor: number
      winAmountMinor?: number
    }
  | { type: 'BALANCE_EXHAUSTED' }
  | { type: 'EXIT_GAME' }

// 每條訊息都會被包在這個信封裡
const BRIDGE_SOURCE = 'moose-platform-game-bridge'
type Envelope<T> = { source: typeof BRIDGE_SOURCE; payload: T }
function wrapEnvelope<T>(payload: T): Envelope<T>
function isBridgeEnvelope(data: unknown): data is Envelope<unknown>

// GameBridge 的 target 選項接受的最小傳送介面——
// 實際使用會傳入 window.parent;測試時則注入樁物件。
type PostMessageTarget = { postMessage(message: unknown, targetOrigin: string): void }

@moosehq/game-client-sdk/behavior

BehaviorCollector

ts
new BehaviorCollector(options?: BehaviorCollectorOptions)

type BehaviorCollectorOptions = {
  now?: () => number // 可注入的時鐘,用於測試;預設為 Date.now
}

把瀏覽器裡的原始互動時間數據彙整成一份摘要,供平台的機器人偵測評分使用。它本身不做任何網路 I/O——搭配下面的 BehaviorReporter(或者直接用 BehaviorReporter,它自己就持有一個收集器)才能真正把摘要送給平台。完整走查見機器人偵測訊號

方法

  • recordInteraction(event?: { isTrusted: boolean }): void —— 記錄一次互動。預設情況下,BehaviorReporter 會替頁面上的每一次點擊自動呼叫這個方法(見下方它的 autoCollectClicks 選項);只有在你關閉了自動採集、想自己控管採集範圍或補充上報時,才需要直接呼叫它。盡量傳入觸發用的 Event:真人點擊的 isTrusted 永遠是 true,而程式化觸發的(例如腳本呼叫 element.click()isTrustedfalse,會被標記為自動化訊號。
  • ready(): boolean —— 累積的訊號足夠回報時為 true:要嘛已經觸發過某個自動化旗標,要嘛已經記錄了至少 3 次互動(2 次互動只能算出 1 個間隔,完全沒有變異數訊號)。
  • takeDigest(): BehaviorDigest —— 把目前的統計視窗彙整成摘要並重置它,讓下一份摘要反映一個全新的視窗,而不是重複計算過去的互動。
ts
type BehaviorDigest = {
  intervalMeanMs: number     // 記錄互動之間的平均毫秒間隔
  intervalStdDevMs: number   // 標準差——機器人的節奏通常又快又規律
  automationFlags: string[]  // 例如 "navigator_webdriver"、"untrusted_interaction_event"
}

有兩個自動化旗標不需要時間視窗就能直接偵測:navigator_webdrivernavigator.webdriver === true,多數瀏覽器自動化工具會設定這個值)和 untrusted_interaction_event(任何一次記錄的互動,其 Event.isTrustedfalse)。

@moosehq/game-client-sdk/behavior-reporter

BehaviorReporter

ts
new BehaviorReporter(options: BehaviorReporterOptions)

type BehaviorReporterOptions = {
  ingestUrl: string           // 平台的行為採集端點——見「環境與基礎網址」
  sessionToken: string        // 以 X-Session-Token 標頭傳送;這個端點唯一的鑑權憑證
  flushIntervalMs?: number    // 預設 15000
  collector?: BehaviorCollector       // 可跨多個 reporter 共用同一個收集器,或注入測試用實例
  collectorOptions?: BehaviorCollectorOptions // 未提供 collector 時,會轉送給 `new BehaviorCollector()`
  fetchImpl?: typeof fetch    // 可注入,用於測試。預設為全域 fetch
  autoCollectClicks?: boolean // 預設 true——自動掛上一個 document 層級的 click 監聽器
}

持有一個 BehaviorCollector,並且是真正跟網路對接的那一層:它把每份摘要直接 POST 到 ingestUrl,用 sessionToken 作為 X-Session-Token 標頭——和這個 SDK 生態圈裡其他呼叫不同,這不是一個簽名呼叫,因為瀏覽器沒辦法保存廠商的 HMAC 金鑰。全程都是盡力而為:任何失敗(網路錯誤、缺少 fetch、SSR/無 DOM 環境)都會被靜默吞掉,絕不會外顯給你的遊戲。

預設情況下(autoCollectClicks: true),建構函式會自動掛上一個 document 層級的 click 監聽器,把頁面上的每一次點擊都記錄為一次互動——除了建構這個 reporter 之外不需要任何其他接入動作。傳入 autoCollectClicks: false 可以跳過這個監聽器,改由你自己透過 recordInteraction 控管採集範圍。

方法

  • recordInteraction(event?: { isTrusted: boolean }): void —— 委派給底層的 BehaviorCollectorautoCollectClicks 開啟時(預設值)不需要呼叫這個方法——每一次點擊都已經透過建構函式掛上的監聽器送進收集器了。這個方法是給關閉了 autoCollectClicks 的呼叫方,或想記錄非 DOM 點擊互動的呼叫方使用的。
  • flush(): void —— 若收集器已經累積足夠訊號(見 BehaviorCollector.ready),立即送出目前的摘要,並重置統計視窗;訊號不足則什麼都不做。這個方法會在計時器、分頁隱藏(document.visibilitychange 變成 'hidden')、以及頁面卸載(window.pagehide)時自動執行——之所以也直接對外開放,是給想額外自訂觸發時機的呼叫方使用。
  • stop(): void —— 取消計時器,並移除 document/window 上的監聽器(包含自動採集的 click 監聽器,如果有掛上的話)。遊戲被卸載時(例如 SPA 路由切換離開前)請呼叫這個方法,避免一個已經失效的 reporter 繼續觸發。

訊息安全性

需要做入站校驗的是 shell 端自己的監聽器(由運營商實現,不屬於這個套件)——它收到的每條訊息在 payload 被信任之前都應該經過三項檢查:

  1. event.origin 精確匹配預期的遊戲 origin。
  2. event.source 確實是你所綁定的那個特定 iframe,而不只是同一個 origin 底下的任意視窗。
  3. isBridgeEnvelope(event.data)——payload 帶有 BRIDGE_SOURCE 信封標記。

只要有一項檢查沒通過,該訊息就應該被靜默忽略,而不是拋出例外——這不是一個「驗證失敗就報錯」的模型。同一頁面上其他不相關的 postMessage 流量(瀏覽器擴充功能、其他嵌入內容)是預期會出現的,你的監聽器對它們應該什麼都不做。