yumi-flutter/lib/modules/room_game/bridge/hotgame_js_bridge.dart
2026-06-12 11:55:13 +08:00

304 lines
10 KiB
Dart

import 'dart:convert';
class HotgameBridgeActions {
static const String recharge = 'recharge';
static const String quit = 'quit';
static const String loadComplete = 'loadComplete';
static const String debugLog = 'debugLog';
static String normalize(String action) {
final trimmed = action.trim();
final lower = trimmed.toLowerCase();
switch (lower) {
case 'recharge':
case 'pay':
case 'gamepay':
case 'gamerecharge':
case 'insufficient':
case 'insufficientbalance':
return recharge;
case 'quit':
case 'exit':
case 'close':
case 'closegame':
case 'destroy':
return quit;
case 'loadcomplete':
case 'gameloaded':
case 'loaded':
case 'ready':
return loadComplete;
case 'debuglog':
return debugLog;
default:
return trimmed;
}
}
}
class HotgameBridgeMessage {
const HotgameBridgeMessage({
required this.action,
this.payload = const <String, dynamic>{},
});
final String action;
final Map<String, dynamic> payload;
static HotgameBridgeMessage fromAction(String action, String rawMessage) {
return HotgameBridgeMessage(
action: HotgameBridgeActions.normalize(action),
payload: _parsePayload(rawMessage),
);
}
static HotgameBridgeMessage parse(String rawMessage) {
try {
final dynamic decoded = jsonDecode(rawMessage);
if (decoded is Map<String, dynamic>) {
return HotgameBridgeMessage(
action: HotgameBridgeActions.normalize(
(decoded['action'] ??
decoded['cmd'] ??
decoded['event'] ??
decoded['method'] ??
decoded['type'] ??
'')
.toString(),
),
payload: _parsePayload(
decoded['payload'] ?? decoded['data'] ?? decoded['params'],
),
);
}
} catch (_) {}
return HotgameBridgeMessage(
action: HotgameBridgeActions.normalize(rawMessage),
);
}
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 HotgameJsBridge {
static const String channelName = 'HotgameBridgeChannel';
static String bootstrapScript({Map<String, dynamic>? launchPayload}) {
final safeLaunchPayload = jsonEncode(
launchPayload ?? const <String, dynamic>{},
);
return '''
(function() {
const launchPayload = $safeLaunchPayload;
function toPayload(input) {
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 stringifyForLog(value) {
if (value == null) {
return '';
}
if (typeof value === 'string') {
return value.length > 320 ? value.slice(0, 320) + '...' : value;
}
try {
const text = JSON.stringify(value);
return text.length > 320 ? text.slice(0, 320) + '...' : text;
} catch (_) {
return String(value);
}
}
function postMessage(action, input) {
const payload = toPayload(input);
$channelName.postMessage(JSON.stringify({
action: action,
payload: payload
}));
return payload;
}
function postDebug(tag, payload) {
postMessage('${HotgameBridgeActions.debugLog}', {
tag: tag,
message: stringifyForLog(payload)
});
}
function dispatchLaunchPayload(payload) {
const normalizedPayload =
payload && typeof payload === 'object' ? payload : {};
const launchConfig =
normalizedPayload.launchConfig &&
typeof normalizedPayload.launchConfig === 'object'
? normalizedPayload.launchConfig
: {};
const entry =
normalizedPayload.entry && typeof normalizedPayload.entry === 'object'
? normalizedPayload.entry
: {};
window.__hotgameLaunchPayload = normalizedPayload;
window.hotgameLaunchPayload = normalizedPayload;
window.__hotgameLaunchConfig = launchConfig;
window.hotgameLaunchConfig = launchConfig;
window.__hotgameLaunchEntry = entry;
window.hotgameLaunchEntry = entry;
if (typeof window.onHotgameLaunchConfig === 'function') {
window.onHotgameLaunchConfig(launchConfig, normalizedPayload);
}
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
window.dispatchEvent(
new CustomEvent('hotgameLaunchConfig', { detail: launchConfig })
);
}
}
function installPostMessageObject(name, action) {
const existing = window[name];
if (!existing || typeof existing.postMessage !== 'function') {
window[name] = {
postMessage: function(body) {
return postMessage(action, body);
}
};
}
}
function installWebkitHandler(name, action) {
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: function(body) {
return postMessage(action, body);
}
};
}
}
function installJsBridgeFunction(name, action) {
window.JsBridge = window.JsBridge || {};
window.JsBridge[name] = function(body) {
return postMessage(action, body || {});
};
}
if (!window.__hotgameBridgeReady) {
window.__hotgameBridgeReady = true;
installJsBridgeFunction('recharge', '${HotgameBridgeActions.recharge}');
installJsBridgeFunction('quit', '${HotgameBridgeActions.quit}');
installJsBridgeFunction('exit', '${HotgameBridgeActions.quit}');
installJsBridgeFunction('closeGame', '${HotgameBridgeActions.quit}');
installPostMessageObject('recharge', '${HotgameBridgeActions.recharge}');
installPostMessageObject('quit', '${HotgameBridgeActions.quit}');
installPostMessageObject('loadComplete', '${HotgameBridgeActions.loadComplete}');
installWebkitHandler('recharge', '${HotgameBridgeActions.recharge}');
installWebkitHandler('quit', '${HotgameBridgeActions.quit}');
installWebkitHandler('loadComplete', '${HotgameBridgeActions.loadComplete}');
window.NativeBridge = window.NativeBridge || {};
window.NativeBridge.recharge = function(body) {
return postMessage('${HotgameBridgeActions.recharge}', body || {});
};
window.NativeBridge.quit = function(body) {
return postMessage('${HotgameBridgeActions.quit}', body || {});
};
window.NativeBridge.closeGame = function(body) {
return postMessage('${HotgameBridgeActions.quit}', body || {});
};
window.NativeBridge.rechargeSuccess = function(body) {
return notifyRechargeSuccess(body || {});
};
window.__hotgameBridgeDebug = postDebug;
postDebug('bridge.ready', {
href: window.location && window.location.href,
hasJsBridge: !!window.JsBridge,
hasRechargeSuccess: typeof window.rechargeSuccess === 'function'
});
}
function notifyRechargeSuccess(body) {
const payload = body || {};
let notified = false;
if (typeof window.rechargeSuccess === 'function') {
window.rechargeSuccess();
notified = true;
}
if (typeof window.onRechargeSuccess === 'function') {
window.onRechargeSuccess(payload);
notified = true;
}
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
window.dispatchEvent(
new CustomEvent('rechargeSuccess', { detail: payload })
);
}
return notified;
}
window.__hotgameNotifyRechargeSuccess = notifyRechargeSuccess;
dispatchLaunchPayload(launchPayload);
})();
''';
}
static String buildRechargeSuccessScript({Map<String, dynamic>? payload}) {
final safePayload = jsonEncode(
payload ??
<String, dynamic>{'timestamp': DateTime.now().millisecondsSinceEpoch},
);
return '''
(function() {
const payload = $safePayload;
if (typeof window.__hotgameBridgeDebug === 'function') {
window.__hotgameBridgeDebug('recharge.success', payload);
}
if (typeof window.__hotgameNotifyRechargeSuccess === 'function') {
window.__hotgameNotifyRechargeSuccess(payload);
return;
}
if (typeof window.rechargeSuccess === 'function') {
window.rechargeSuccess();
}
if (typeof window.onRechargeSuccess === 'function') {
window.onRechargeSuccess(payload);
}
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
window.dispatchEvent(
new CustomEvent('rechargeSuccess', { detail: payload })
);
}
})();
''';
}
}