import 'dart:convert'; import 'package:yumi/modules/room_game/data/models/room_game_models.dart'; class BaishunBridgeActions { static const String getConfig = 'getConfig'; static const String gameLoaded = 'gameLoaded'; static const String gameRecharge = 'gameRecharge'; static const String destroy = 'destroy'; static const String debugLog = 'debugLog'; } class BaishunBridgeMessage { const BaishunBridgeMessage({ required this.action, this.payload = const {}, }); final String action; final Map payload; static BaishunBridgeMessage fromAction(String action, String rawMessage) { return BaishunBridgeMessage( action: action, payload: _parsePayload(rawMessage), ); } static BaishunBridgeMessage parse(String rawMessage) { try { final dynamic decoded = jsonDecode(rawMessage); if (decoded is Map) { return BaishunBridgeMessage( action: (decoded['action'] ?? decoded['cmd'] ?? decoded['event'] ?? '') .toString() .trim(), payload: _parsePayload( decoded['payload'] ?? decoded['data'] ?? decoded['params'], ), ); } } catch (_) {} return BaishunBridgeMessage(action: rawMessage.trim()); } static Map _parsePayload(dynamic rawPayload) { if (rawPayload is Map) { return rawPayload; } if (rawPayload is String) { final trimmed = rawPayload.trim(); if (trimmed.isEmpty) { return const {}; } try { final dynamic decoded = jsonDecode(trimmed); if (decoded is Map) { return decoded; } } catch (_) {} return {'raw': trimmed}; } return const {}; } } class BaishunJsBridge { static const String channelName = 'BaishunBridgeChannel'; static String bootstrapScript() { return ''' (function() { if (window.__baishunBridgeReady) { return; } window.__baishunBridgeReady = true; window.NativeBridge = window.NativeBridge || {}; function toPayload(input) { if (typeof input === 'function') { window.__baishunGetConfigCallback = input; return {}; } if (typeof input === 'string') { try { return JSON.parse(input); } catch (_) { return input.trim() ? { raw: input.trim() } : {}; } } if (input && typeof input === 'object') { return input; } return {}; } function rememberJsCallback(payload) { if (payload && typeof payload.jsCallback === 'string' && payload.jsCallback.trim()) { window.__baishunLastJsCallback = payload.jsCallback.trim(); } } function postAction(action, input) { const payload = toPayload(input); rememberJsCallback(payload); $channelName.postMessage(JSON.stringify({ action: action, payload: payload })); return payload; } function clipText(value, limit) { const text = (value == null ? '' : String(value)).trim(); if (!text) { return ''; } return text.length > limit ? text.slice(0, limit) + '...' : text; } function stringifyForLog(value) { if (value == null) { return ''; } if (typeof value === 'string') { return clipText(value, 320); } try { return clipText(JSON.stringify(value), 320); } catch (_) { return clipText(String(value), 320); } } function postDebug(tag, payload) { $channelName.postMessage(JSON.stringify({ action: '${BaishunBridgeActions.debugLog}', payload: { tag: tag, message: stringifyForLog(payload) } })); } function ensureChannelAlias(name, handler) { if (!window[name] || typeof window[name].postMessage !== 'function') { window[name] = { postMessage: handler }; } } function ensureWebkitHandler(name, handler) { window.webkit = window.webkit || {}; window.webkit.messageHandlers = window.webkit.messageHandlers || {}; if (!window.webkit.messageHandlers[name] || typeof window.webkit.messageHandlers[name].postMessage !== 'function') { window.webkit.messageHandlers[name] = { postMessage: handler }; } } window.NativeBridge.getConfigSync = function() { return window.__baishunLastConfig || null; }; window.NativeBridge.getConfig = function(params) { postAction('${BaishunBridgeActions.getConfig}', params); return window.__baishunLastConfig || null; }; window.NativeBridge.destroy = function(payload) { postAction('${BaishunBridgeActions.destroy}', payload); }; window.NativeBridge.gameRecharge = function(payload) { postAction('${BaishunBridgeActions.gameRecharge}', payload); }; window.NativeBridge.gameLoaded = function(payload) { postAction('${BaishunBridgeActions.gameLoaded}', payload); }; window.__baishunDebugLog = postDebug; ensureChannelAlias('getConfig', function(message) { window.NativeBridge.getConfig(message); }); ensureChannelAlias('destroy', function(message) { window.NativeBridge.destroy(message); }); ensureChannelAlias('gameRecharge', function(message) { window.NativeBridge.gameRecharge(message); }); ensureChannelAlias('gameLoaded', function(message) { window.NativeBridge.gameLoaded(message); }); ensureWebkitHandler('getConfig', function(message) { window.NativeBridge.getConfig(message); }); ensureWebkitHandler('destroy', function(message) { window.NativeBridge.destroy(message); }); ensureWebkitHandler('gameRecharge', function(message) { window.NativeBridge.gameRecharge(message); }); ensureWebkitHandler('gameLoaded', function(message) { window.NativeBridge.gameLoaded(message); }); if (!window.__baishunNetworkDebugReady) { window.__baishunNetworkDebugReady = true; if (typeof document !== 'undefined' && typeof document.addEventListener === 'function' && !window.__baishunInputDebugReady) { window.__baishunInputDebugReady = true; window.__baishunInputDebugCount = 0; ['pointerdown', 'touchstart', 'mousedown', 'click'].forEach(function(eventName) { document.addEventListener(eventName, function(event) { if ((window.__baishunInputDebugCount || 0) >= 24) { return; } window.__baishunInputDebugCount = (window.__baishunInputDebugCount || 0) + 1; const target = event && event.target; const touch = event && event.touches && event.touches.length ? event.touches[0] : null; postDebug('input.' + eventName, { x: touch ? touch.clientX : (event && event.clientX), y: touch ? touch.clientY : (event && event.clientY), pointerType: event && event.pointerType, targetTag: target && target.tagName, targetId: target && target.id, targetClass: target && target.className }); }, true); }); } if (typeof window.addEventListener === 'function') { window.addEventListener('error', function(event) { const target = event && event.target; if (target && target !== window) { postDebug('resource.error', { tag: target.tagName || '', source: target.currentSrc || target.src || target.href || '', outerHTML: target.outerHTML || '' }); return; } postDebug('window.error', { message: event && event.message, source: event && event.filename, line: event && event.lineno, column: event && event.colno, stack: event && event.error && event.error.stack }); }, true); window.addEventListener('unhandledrejection', function(event) { postDebug('promise.reject', { reason: event && event.reason }); }); } if (window.console && !window.__baishunConsoleDebugReady) { window.__baishunConsoleDebugReady = true; ['log', 'info', 'warn', 'error'].forEach(function(level) { const original = typeof window.console[level] === 'function' ? window.console[level].bind(window.console) : null; window.console[level] = function() { try { postDebug('console.' + level, Array.prototype.slice.call(arguments)); } catch (_) {} if (original) { return original.apply(window.console, arguments); } return undefined; }; }); } if (typeof window.fetch === 'function') { const originalFetch = window.fetch.bind(window); window.fetch = function(input, init) { const url = typeof input === 'string' ? input : (input && input.url) || ''; const method = (init && init.method) || 'GET'; postDebug('fetch.start', { method: method, url: url }); return originalFetch(input, init) .then(function(response) { const cloned = response && typeof response.clone === 'function' ? response.clone() : null; if (cloned && typeof cloned.text === 'function') { cloned.text().then(function(text) { if (!response.ok || /failed|error|exception|get user info/i.test(text)) { postDebug('fetch.end', { method: method, url: url, status: response.status, body: text }); } else { postDebug('fetch.end', { method: method, url: url, status: response.status }); } }).catch(function(error) { postDebug('fetch.readError', { method: method, url: url, error: error && error.message ? error.message : String(error) }); }); } else { postDebug('fetch.end', { method: method, url: url, status: response && response.status }); } return response; }) .catch(function(error) { postDebug('fetch.error', { method: method, url: url, error: error && error.message ? error.message : String(error) }); throw error; }); }; } if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) { const xhrOpen = window.XMLHttpRequest.prototype.open; const xhrSend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.open = function(method, url) { this.__baishunMethod = method || 'GET'; this.__baishunUrl = url || ''; return xhrOpen.apply(this, arguments); }; window.XMLHttpRequest.prototype.send = function(body) { const method = this.__baishunMethod || 'GET'; const url = this.__baishunUrl || ''; postDebug('xhr.start', { method: method, url: url }); this.addEventListener('loadend', function() { const responseText = typeof this.responseText === 'string' ? this.responseText : ''; if (this.status >= 400 || /failed|error|exception|get user info/i.test(responseText)) { postDebug('xhr.end', { method: method, url: url, status: this.status, body: responseText }); } else { postDebug('xhr.end', { method: method, url: url, status: this.status }); } }); this.addEventListener('error', function() { postDebug('xhr.error', { method: method, url: url, status: this.status }); }); return xhrSend.apply(this, arguments); }; } } window.baishunChannel = window.baishunChannel || {}; window.baishunChannel.walletUpdate = function(payload) { window.__baishunLastWalletPayload = payload || {}; if (typeof window.walletUpdate === 'function') { window.walletUpdate(payload || {}); } if (typeof window.onWalletUpdate === 'function') { window.onWalletUpdate(payload || {}); } if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent('walletUpdate', { detail: payload || {} })); } }; if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent('baishunBridgeReady')); } })(); '''; } static String buildConfigScript( BaishunBridgeConfigModel config, { String? jsCallbackPath, }) { final payload = jsonEncode(config.toJson()); final encodedCallbackPath = jsonEncode(jsCallbackPath?.trim() ?? ''); return ''' (function() { const config = $payload; const explicitCallbackPath = $encodedCallbackPath; function resolvePath(path) { if (!path || typeof path !== 'string') { return null; } const parts = path.split('.').filter(Boolean); if (!parts.length) { return null; } let scope = window; for (let i = 0; i < parts.length - 1; i += 1) { scope = scope ? scope[parts[i]] : null; } if (!scope) { return null; } const methodName = parts[parts.length - 1]; const method = scope[methodName]; if (typeof method !== 'function') { return null; } return { scope: scope, method: method }; } function invokeCallbackPath(path) { const target = resolvePath(path); if (!target) { return false; } target.method.call(target.scope, config); return true; } window.__baishunLastConfig = config; window.baishunBridgeConfig = config; window.NativeBridge = window.NativeBridge || {}; window.NativeBridge.config = config; if (explicitCallbackPath) { window.__baishunLastJsCallback = explicitCallbackPath; } if (typeof window.__baishunGetConfigCallback === 'function') { window.__baishunGetConfigCallback(config); window.__baishunGetConfigCallback = null; } if (window.__baishunLastJsCallback) { invokeCallbackPath(window.__baishunLastJsCallback); } if (typeof window.onBaishunConfig === 'function') { window.onBaishunConfig(config); } if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent('baishunConfig', { detail: config })); } if (typeof document !== 'undefined' && typeof document.dispatchEvent === 'function' && typeof CustomEvent === 'function') { document.dispatchEvent(new CustomEvent('baishunConfig', { detail: config })); } })(); '''; } static String buildWalletUpdateScript({Map? payload}) { final safePayload = jsonEncode( payload ?? {'timestamp': DateTime.now().millisecondsSinceEpoch}, ); return ''' (function() { const payload = $safePayload; if (window.baishunChannel && typeof window.baishunChannel.walletUpdate === 'function') { window.baishunChannel.walletUpdate(payload); } })(); '''; } }