chatapp3-flutter/lib/modules/room_game/bridge/baishun_js_bridge.dart
2026-04-16 15:07:47 +08:00

426 lines
16 KiB
Dart

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 <String, dynamic>{},
});
final String action;
final Map<String, dynamic> 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<String, dynamic>) {
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<String, dynamic> _parsePayload(dynamic rawPayload) {
if (rawPayload is Map<String, dynamic>) {
return rawPayload;
}
if (rawPayload is String) {
final trimmed = rawPayload.trim();
if (trimmed.isEmpty) {
return const <String, dynamic>{};
}
try {
final dynamic decoded = jsonDecode(trimmed);
if (decoded is Map<String, dynamic>) {
return decoded;
}
} catch (_) {}
return <String, dynamic>{'raw': trimmed};
}
return const <String, dynamic>{};
}
}
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 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<String, dynamic>? payload}) {
final safePayload = jsonEncode(
payload ??
<String, dynamic>{'timestamp': DateTime.now().millisecondsSinceEpoch},
);
return '''
(function() {
const payload = $safePayload;
if (window.baishunChannel && typeof window.baishunChannel.walletUpdate === 'function') {
window.baishunChannel.walletUpdate(payload);
}
})();
''';
}
}