chatapp3-flutter/lib/modules/room_game/bridge/leader_js_bridge.dart
2026-04-22 20:08:45 +08:00

276 lines
9.0 KiB
Dart

import 'dart:convert';
class LeaderBridgeActions {
static const String getConfig = 'getConfig';
static const String pay = 'pay';
static const String closeGame = 'closeGame';
static const String loadComplete = 'loadComplete';
static const String debugLog = 'debugLog';
}
class LeaderBridgeMessage {
const LeaderBridgeMessage({
required this.action,
this.payload = const <String, dynamic>{},
});
final String action;
final Map<String, dynamic> payload;
static LeaderBridgeMessage fromAction(String action, String rawMessage) {
return LeaderBridgeMessage(
action: action,
payload: _parsePayload(rawMessage),
);
}
static LeaderBridgeMessage parse(String rawMessage) {
try {
final dynamic decoded = jsonDecode(rawMessage);
if (decoded is Map<String, dynamic>) {
return LeaderBridgeMessage(
action:
(decoded['action'] ?? decoded['cmd'] ?? decoded['event'] ?? '')
.toString()
.trim(),
payload: _parsePayload(
decoded['payload'] ?? decoded['data'] ?? decoded['params'],
),
);
}
} catch (_) {}
return LeaderBridgeMessage(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 LeaderJsBridge {
static const String channelName = 'LeaderBridgeChannel';
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('${LeaderBridgeActions.debugLog}', {
tag: tag,
message: stringifyForLog(payload)
});
}
function invokeCallback(callback, payload) {
if (typeof callback === 'function') {
callback(payload);
return true;
}
return false;
}
function dispatchConfigReady(payload) {
if (typeof window.onLeaderLaunchConfig === 'function') {
window.onLeaderLaunchConfig(payload);
}
if (typeof window.onLaunchConfig === 'function') {
window.onLaunchConfig(payload.launchConfig || {}, payload);
}
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
window.dispatchEvent(
new CustomEvent('leaderLaunchConfig', { detail: payload })
);
window.dispatchEvent(
new CustomEvent('launchConfig', {
detail: payload.launchConfig || {}
})
);
}
if (typeof document !== 'undefined' &&
typeof document.dispatchEvent === 'function' &&
typeof CustomEvent === 'function') {
document.dispatchEvent(
new CustomEvent('leaderLaunchConfig', { detail: payload })
);
document.dispatchEvent(
new CustomEvent('launchConfig', {
detail: payload.launchConfig || {}
})
);
}
}
function applyLaunchPayload(payload) {
const normalizedPayload =
payload && typeof payload === 'object' ? payload : {};
const payloadText = JSON.stringify(normalizedPayload);
const payloadChanged = window.__leaderLaunchPayloadText !== payloadText;
const launchConfig =
normalizedPayload.launchConfig &&
typeof normalizedPayload.launchConfig === 'object'
? normalizedPayload.launchConfig
: {};
const entry =
normalizedPayload.entry && typeof normalizedPayload.entry === 'object'
? normalizedPayload.entry
: {};
window.__leaderLaunchPayload = normalizedPayload;
window.__leaderLaunchPayloadText = payloadText;
window.leaderLaunchPayload = normalizedPayload;
window.__leaderLaunchConfig = launchConfig;
window.leaderLaunchConfig = launchConfig;
window.__leaderLaunchEntry = entry;
window.leaderLaunchEntry = entry;
window.NativeBridge = window.NativeBridge || {};
window.NativeBridge.config = launchConfig;
window.NativeBridge.launchConfig = launchConfig;
window.NativeBridge.entry = entry;
window.NativeBridge.getConfig = function(callback) {
invokeCallback(callback, launchConfig);
return launchConfig;
};
window.NativeBridge.getLaunchPayload = function(callback) {
invokeCallback(callback, normalizedPayload);
return normalizedPayload;
};
window.getConfig = function(callback) {
return window.NativeBridge.getConfig(callback);
};
window.getConfig.postMessage = function(callback) {
return window.NativeBridge.getConfig(callback);
};
if (payloadChanged) {
dispatchConfigReady(normalizedPayload);
postDebug('config.ready', {
provider: normalizedPayload.provider || '',
gameId: normalizedPayload.gameId || '',
roomId: launchConfig.roomId || '',
code: launchConfig.code
? String(launchConfig.code).slice(0, 6) + '***'
: '',
codeLength: launchConfig.code ? String(launchConfig.code).length : 0,
entryUrl: entry.entryUrl || ''
});
}
}
if (!window.__leaderBridgeReady) {
window.__leaderBridgeReady = true;
function bindCallable(name) {
const callable = function(payload) {
return postMessage(name, payload);
};
callable.postMessage = function(payload) {
return callable(payload);
};
window[name] = callable;
return callable;
}
const pay = bindCallable('${LeaderBridgeActions.pay}');
const closeGame = bindCallable('${LeaderBridgeActions.closeGame}');
const loadComplete = bindCallable('${LeaderBridgeActions.loadComplete}');
window.NativeBridge = window.NativeBridge || {};
window.NativeBridge.pay = pay;
window.NativeBridge.closeGame = closeGame;
window.NativeBridge.loadComplete = loadComplete;
window.__leaderBridgeDebug = postDebug;
postDebug('bridge.ready', {
href: window.location && window.location.href,
hasUpdateCoin: typeof window.updateCoin === 'function'
});
}
applyLaunchPayload(launchPayload);
})();
''';
}
static String buildUpdateCoinScript() {
return '''
(function() {
if (typeof window.updateCoin === 'function') {
window.updateCoin();
return;
}
if (window.NativeBridge && typeof window.NativeBridge.updateCoin === 'function') {
window.NativeBridge.updateCoin();
return;
}
if (typeof window.onUpdateCoin === 'function') {
window.onUpdateCoin();
return;
}
$channelName.postMessage(JSON.stringify({
action: '${LeaderBridgeActions.debugLog}',
payload: {
tag: 'game.updateCoin.missing',
message: 'No updateCoin handler found on game page.'
}
}));
})();
''';
}
}