跳转到内容

宿主桥

游戏页面运行在你 App 的 WebView 中,通过一个小型 JSBridge 与 App 通信。它沿用大多数语聊 App 已经实现的 BAISHUN(百顺)1.3.2 JSBridge 格式;如果你已经在接入其他 H5 游戏,很可能已经具备。

用游戏入口地址打开游戏(见游戏目录):

https://sandbox.sentosatower.com/games/<gameId>/index.html?op=<operatorId>
https://games.sentosatower.com/games/<gameId>/index.html?op=<operatorId>
方法 页面调用方式 App 应当
getConfig Android / Flutter:NativeBridge.getConfig(JSON.stringify({ jsCallback }))
iOS:webkit.messageHandlers.getConfig.postMessage({ jsCallback })
申请一个新的启动码,然后调用 window[jsCallback]("<配置 JSON 字符串>")
gameLoaded NativeBridge.gameLoaded() / messageHandlers.gameLoaded.postMessage({}) 首帧就绪:关闭你的 loading
gameRecharge 同上 打开充值页(余额不足或玩家点击余额)
destroy 同上 关闭游戏并释放 WebView

可选扩展——未实现时页面静默忽略。页面传入一个 JSON 字符串参数:

方法 参数 用途
haptic {"type":"light" | "success" | "heavy"} 振动
roomEvent {"type":"bigwin","gameId","name","amount","roundId"} 在房间内播报大奖
displayMode {"mode":"half" | "full","height"?} 玩家切换了半屏 / 全屏(例如调整触摸穿透)

App → 页面:充值成功后调用 window.walletUpdate({}),游戏会向平台刷新余额。

字段 必填 说明
code 是 启动码:由你的后端签发,不透明、一次性、有效期 ≤ 5 分钟。平台通过 POST /players/authenticate 兑换。
userId 建议 你的玩家 ID(展示和排障用;身份以 authenticate 返回为准)
roomId 可选 当前语聊房,会作为交易的 room_id 带回
language 建议 ISO 语言码(en、ar、zh-CN 等)或百顺数字码
gameMode 可选 2 半屏,3 全屏
safeHeight、safeHeightUnit 可选 半屏可用高度;单位 css(CSS 像素)或 design750(750 宽设计稿像素)
appId、appChannel、gameConfig、gsp 可选 百顺兼容字段,接受但不使用
{ "code": "lc_7Qm2…", "userId": "84021734", "roomId": "8880085", "language": "ar", "gameMode": 2, "safeHeight": 420, "safeHeightUnit": "css" }
  • 桥要早于页面脚本。 如果你的 App 注入桥较晚(页面开始加载之后才注入,Flutter 和部分 Android 封装就是这样),请在入口地址后追加 uid=<userId>&time=<毫秒时间戳>。两个参数都存在时,页面会最多等待 3 秒让桥出现,而不是直接退回浏览器模式。
  • getConfig 8 秒内应答。 如果 NativeBridge.getConfig 同步返回非空 JSON 字符串,页面直接使用;否则等待 jsCallback 回调,最多 8 秒,超时显示进入失败。向你的后端申请启动码的耗时建议控制在 5 秒以内。
  • 不要用旧脚本。 游戏脚本以 Cache-Control: no-cache 和带版本号的地址提供,不要强制 WebView 缓存。
  • 每次打开游戏都重新调用 getConfig:启动码只能用一次。
class StarGameBridge(private val activity: Activity, private val webView: WebView) {
@JavascriptInterface
fun getConfig(params: String?): String {
val callback = params?.let { JSONObject(it).optString("jsCallback") }.orEmpty()
// Ask your backend for a one-time launch code (off the UI thread).
launchCodes.issue(currentUser.id) { code ->
val config = JSONObject()
.put("code", code)
.put("userId", currentUser.id)
.put("roomId", currentRoom.id)
.put("language", Locale.getDefault().language)
.put("gameMode", 2)
.put("safeHeight", halfScreenHeightCssPx)
.put("safeHeightUnit", "css")
val args = JSONArray().put(callback).put(config.toString())
webView.post { webView.evaluateJavascript("(function(a){window[a[0]](a[1]);})($args)", null) }
}
return "" // empty: the page waits for the callback
}
@JavascriptInterface fun gameLoaded() = activity.runOnUiThread { hideLoading() }
@JavascriptInterface fun gameRecharge() = activity.runOnUiThread { openTopUp() }
@JavascriptInterface fun destroy() = activity.runOnUiThread { closeGame() }
@JavascriptInterface fun haptic(payload: String) { /* optional */ }
@JavascriptInterface fun displayMode(payload: String) { /* optional */ }
}
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.addJavascriptInterface(StarGameBridge(activity, webView), "NativeBridge")
webView.loadUrl("https://sandbox.sentosatower.com/games/lantern/index.html?op=$operatorId")

不用 App 也可以验证你的启动码。在入口地址后追加 &uid=1&time=1 打开,并在页面脚本运行前注入下面的代码(例如用设为 document-start 执行的用户脚本管理器):

window.NativeBridge = {
getConfig(p) {
const cb = JSON.parse(p).jsCallback;
setTimeout(() => window[cb](JSON.stringify({ code: "<launch code>", userId: "<player id>", gameMode: 2, language: "en" })), 20);
return "";
},
gameLoaded() { console.log("host: gameLoaded"); },
gameRecharge() { console.log("host: gameRecharge"); },
destroy() { console.log("host: destroy"); },
};

也可以用 Playwright 自动化:

import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
page.on("console", (m) => console.log(m.text()));
await page.addInitScript((code) => {
window.NativeBridge = {
getConfig(p) {
const cb = JSON.parse(p).jsCallback;
setTimeout(() => window[cb](JSON.stringify({ code, userId: "self-test", gameMode: 2, language: "en" })), 20);
return "";
},
gameLoaded() { console.log("host: gameLoaded"); },
gameRecharge() {},
destroy() {},
};
}, process.env.LAUNCH_CODE);
await page.goto(`https://sandbox.sentosatower.com/games/lantern/index.html?op=${process.env.OPERATOR_ID}&uid=1&time=1`);
await page.waitForTimeout(5000);
await page.screenshot({ path: "lantern.png" });
await browser.close();

预期:约 3 秒内出现下注界面,控制台打印 host: gameLoaded,你的钱包收到一次针对该启动码的 POST /players/authenticate。