From 47f8f10fa57e56c11882a36894568883e4b8f971 Mon Sep 17 00:00:00 2001 From: hy001 Date: Thu, 16 Apr 2026 12:11:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=99=BD=E9=A1=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/debug/baishun_mock/index.html | 23 ++++++ .../room_game/bridge/baishun_js_bridge.dart | 36 ++++++++- .../room_game/views/baishun_game_page.dart | 76 +++++++++++++++---- 3 files changed, 117 insertions(+), 18 deletions(-) diff --git a/assets/debug/baishun_mock/index.html b/assets/debug/baishun_mock/index.html index 4167b8d..5a53a5a 100644 --- a/assets/debug/baishun_mock/index.html +++ b/assets/debug/baishun_mock/index.html @@ -88,6 +88,8 @@ logEl.textContent = line + logEl.textContent; } + let hasLoadedGame = false; + function requestConfig() { if (!window.NativeBridge || typeof window.NativeBridge.getConfig !== "function") { writeLog("getConfig", { error: "NativeBridge not ready" }); @@ -95,10 +97,14 @@ } window.NativeBridge.getConfig(function (config) { writeLog("getConfig callback", config); + if (!hasLoadedGame) { + setTimeout(notifyLoaded, 300); + } }); } function notifyLoaded() { + hasLoadedGame = true; window.NativeBridge && window.NativeBridge.gameLoaded && window.NativeBridge.gameLoaded({ status: "loaded" }); @@ -126,6 +132,23 @@ window.addEventListener("walletUpdate", function (event) { writeLog("walletUpdate event", event.detail || {}); }); + + window.addEventListener("baishunBridgeReady", function () { + writeLog("baishunBridgeReady", { status: "received" }); + requestConfig(); + }); + + window.addEventListener("baishunConfig", function (event) { + writeLog("baishunConfig event", event.detail || {}); + }); + + window.addEventListener("load", function () { + setTimeout(function () { + if (!hasLoadedGame) { + requestConfig(); + } + }, 150); + }); diff --git a/lib/modules/room_game/bridge/baishun_js_bridge.dart b/lib/modules/room_game/bridge/baishun_js_bridge.dart index a5e3f8e..973df5b 100644 --- a/lib/modules/room_game/bridge/baishun_js_bridge.dart +++ b/lib/modules/room_game/bridge/baishun_js_bridge.dart @@ -76,6 +76,12 @@ class BaishunJsBridge { window.dispatchEvent(new CustomEvent('walletUpdate', { detail: payload || {} })); } }; + if (typeof window.onBaishunBridgeReady === 'function') { + window.onBaishunBridgeReady(window.NativeBridge); + } + if (typeof window.dispatchEvent === 'function') { + window.dispatchEvent(new CustomEvent('baishunBridgeReady', { detail: { nativeBridge: true } })); + } })(); '''; } @@ -88,11 +94,13 @@ class BaishunJsBridge { window.__baishunLastConfig = config; if (typeof window.__baishunGetConfigCallback === 'function') { window.__baishunGetConfigCallback(config); - return; } if (typeof window.onBaishunConfig === 'function') { window.onBaishunConfig(config); } + if (typeof window.dispatchEvent === 'function') { + window.dispatchEvent(new CustomEvent('baishunConfig', { detail: config })); + } })(); '''; } @@ -100,9 +108,7 @@ class BaishunJsBridge { static String buildWalletUpdateScript({Map? payload}) { final safePayload = jsonEncode( payload ?? - { - 'timestamp': DateTime.now().millisecondsSinceEpoch, - }, + {'timestamp': DateTime.now().millisecondsSinceEpoch}, ); return ''' (function() { @@ -113,4 +119,26 @@ class BaishunJsBridge { })(); '''; } + + static String injectBootstrapHtml({ + required String html, + required Uri entryUri, + required BaishunBridgeConfigModel config, + }) { + final baseHref = htmlEscape.convert(entryUri.toString()); + final injection = ''' + + + '''; + + final headPattern = RegExp(r']*>', caseSensitive: false); + final headMatch = headPattern.firstMatch(html); + if (headMatch != null) { + return html.replaceRange(headMatch.end, headMatch.end, injection); + } + return '$injection$html'; + } } diff --git a/lib/modules/room_game/views/baishun_game_page.dart b/lib/modules/room_game/views/baishun_game_page.dart index 75d4211..0bb982e 100644 --- a/lib/modules/room_game/views/baishun_game_page.dart +++ b/lib/modules/room_game/views/baishun_game_page.dart @@ -1,4 +1,7 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -49,7 +52,7 @@ class _BaishunGamePageState extends State { ..setNavigationDelegate( NavigationDelegate( onPageFinished: (String _) async { - await _injectBridge(); + await _primeBridgeHandshake(); }, onWebResourceError: (WebResourceError error) { if (!mounted) { @@ -83,7 +86,13 @@ class _BaishunGamePageState extends State { if (uri == null) { throw Exception('Invalid game entry url: $entryUrl'); } - await _controller.loadRequest(uri); + final html = await _fetchRemoteEntryHtml(uri); + final injectedHtml = BaishunJsBridge.injectBootstrapHtml( + html: html, + entryUri: uri, + config: widget.launchModel.bridgeConfig, + ); + await _controller.loadHtmlString(injectedHtml, baseUrl: uri.toString()); } catch (error) { if (!mounted) { return; @@ -95,6 +104,24 @@ class _BaishunGamePageState extends State { } } + Future _fetchRemoteEntryHtml(Uri uri) async { + final client = HttpClient(); + client.userAgent = 'YumiBaishunBridge/1.0'; + try { + final request = await client.getUrl(uri); + final response = await request.close(); + if (response.statusCode < 200 || response.statusCode >= 300) { + throw HttpException( + 'Failed to load remote entry html (${response.statusCode})', + uri: uri, + ); + } + return response.transform(utf8.decoder).join(); + } finally { + client.close(force: true); + } + } + Future _injectBridge() async { try { await _controller.runJavaScript(BaishunJsBridge.bootstrapScript()); @@ -104,6 +131,21 @@ class _BaishunGamePageState extends State { } catch (_) {} } + Future _primeBridgeHandshake() async { + await _injectBridge(); + for (final delay in [ + const Duration(milliseconds: 400), + const Duration(milliseconds: 1200), + ]) { + Future.delayed(delay, () async { + if (!mounted || !_isLoading || _errorMessage != null) { + return; + } + await _injectBridge(); + }); + } + } + Future _handleBridgeMessage(JavaScriptMessage message) async { final bridgeMessage = BaishunBridgeMessage.parse(message.message); switch (bridgeMessage.action) { @@ -135,7 +177,9 @@ class _BaishunGamePageState extends State { Future _notifyWalletUpdate() async { try { - await _controller.runJavaScript(BaishunJsBridge.buildWalletUpdateScript()); + await _controller.runJavaScript( + BaishunJsBridge.buildWalletUpdateScript(), + ); } catch (_) {} } @@ -206,7 +250,10 @@ class _BaishunGamePageState extends State { children: [ IconButton( onPressed: _closeAndExit, - icon: const Icon(Icons.arrow_back_ios, color: Colors.white), + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), ), Expanded( child: Text( @@ -229,10 +276,17 @@ class _BaishunGamePageState extends State { Expanded( child: Stack( children: [ - Positioned.fill(child: WebViewWidget(controller: _controller)), + Positioned.fill( + child: WebViewWidget(controller: _controller), + ), if (_errorMessage != null) _buildErrorState(), if (_isLoading && _errorMessage == null) - const BaishunLoadingView(message: 'Waiting for gameLoaded...'), + const IgnorePointer( + ignoring: true, + child: BaishunLoadingView( + message: 'Waiting for gameLoaded...', + ), + ), ], ), ), @@ -266,19 +320,13 @@ class _BaishunGamePageState extends State { Text( _errorMessage ?? '', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white70, - fontSize: 12.sp, - ), + style: TextStyle(color: Colors.white70, fontSize: 12.sp), ), SizedBox(height: 16.w), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - TextButton( - onPressed: _reload, - child: const Text('Retry'), - ), + TextButton(onPressed: _reload, child: const Text('Retry')), SizedBox(width: 10.w), TextButton( onPressed: () {