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 {}, }); final String action; final Map 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) { 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 _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 LeaderJsBridge { static const String channelName = 'LeaderBridgeChannel'; static String bootstrapScript({Map? launchPayload}) { final safeLaunchPayload = jsonEncode( launchPayload ?? const {}, ); 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.' } })); })(); '''; } }