304 lines
10 KiB
Dart
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 })
|
|
);
|
|
}
|
|
})();
|
|
''';
|
|
}
|
|
}
|